asciimath 1.0.0.preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/asciimath.gemspec +24 -0
- data/bin/asciimath +4 -0
- data/lib/asciimath.rb +5 -0
- data/lib/asciimath/cli.rb +12 -0
- data/lib/asciimath/mathml.rb +126 -0
- data/lib/asciimath/parser.rb +529 -0
- data/lib/asciimath/version.rb +3 -0
- data/test/parser_spec.rb +67 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 79e61b8558a971dd887b6e48246a1ee34be6098e
|
4
|
+
data.tar.gz: c7f13147dbb2460cf4ab5564249a0391fdb57095
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c7a2f710248ecf71df6a124aecf8e96e0e626b7448831166212572a24cccf615ef85c2f3749531499e3dc2155122c5b219dba012a6cc264de668532ab869962
|
7
|
+
data.tar.gz: 8350a5355cdf93da68d7c9224c8cc820a2fd01b2edee38cd758f6911c75e577f63d5a216b9e2d114c308bb8bd07af95ea1d717b55fd097afedb7b565de8284a4
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Pepijn Van Eeckhoudt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Asciimath
|
2
|
+
|
3
|
+
An [asciimath](http://asciimath.org) parser and MathML generator written in pure Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'asciimath'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install asciimath
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/asciimath/fork )
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/asciimath.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'asciimath/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "asciimath"
|
8
|
+
spec.version = Asciimath::VERSION
|
9
|
+
spec.authors = ["Pepijn Van Eeckhoudt"]
|
10
|
+
spec.email = ["pepijn@vaneeckhoudt.net"]
|
11
|
+
spec.summary = %q{Asciimath parser and converter}
|
12
|
+
spec.description = %q{A pure Ruby Asciimath parsing and conversion library.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.1.0"
|
24
|
+
end
|
data/bin/asciimath
ADDED
data/lib/asciimath.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module Asciimath
|
2
|
+
class MathMLBuilder
|
3
|
+
def initialize(prefix)
|
4
|
+
@prefix = prefix
|
5
|
+
@mathml = ''
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
@mathml
|
10
|
+
end
|
11
|
+
|
12
|
+
def append_expression(expression, attrs = {})
|
13
|
+
math('', attrs) do
|
14
|
+
append(expression, :single_child => true)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def append(expression, opts = {})
|
21
|
+
case expression
|
22
|
+
when Array
|
23
|
+
if expression.length <= 1 || opts[:single_child]
|
24
|
+
expression.each { |e| append(e) }
|
25
|
+
else
|
26
|
+
mrow do
|
27
|
+
expression.each { |e| append(e) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
when Hash
|
31
|
+
case expression[:type]
|
32
|
+
when :operator
|
33
|
+
mo(expression[:c])
|
34
|
+
when :identifier
|
35
|
+
mi(expression[:c])
|
36
|
+
when :number
|
37
|
+
mn(expression[:c])
|
38
|
+
when :text
|
39
|
+
mtext(expression[:c])
|
40
|
+
when :paren
|
41
|
+
paren = !opts[:strip_paren]
|
42
|
+
if paren
|
43
|
+
if opts[:single_child]
|
44
|
+
mo(expression[:lparen]) if expression[:lparen]
|
45
|
+
append(expression[:e], :single_child => true)
|
46
|
+
mo(expression[:rparen]) if expression[:rparen]
|
47
|
+
else
|
48
|
+
mrow do
|
49
|
+
mo(expression[:lparen]) if expression[:lparen]
|
50
|
+
append(expression[:e], :single_child => true)
|
51
|
+
mo(expression[:rparen]) if expression[:rparen]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
append(expression[:e])
|
56
|
+
end
|
57
|
+
when :unary
|
58
|
+
operator = expression[:operator]
|
59
|
+
tag("m#{operator}") do
|
60
|
+
append(expression[:s], :single_child => true, :strip_paren => true)
|
61
|
+
end
|
62
|
+
when :binary
|
63
|
+
operator = expression[:operator]
|
64
|
+
tag("m#{operator}") do
|
65
|
+
append(expression[:s1], :strip_paren => (operator != :sub && operator != :sup))
|
66
|
+
append(expression[:s2], :strip_paren => true)
|
67
|
+
end
|
68
|
+
when :ternary
|
69
|
+
operator = expression[:operator]
|
70
|
+
tag("m#{operator}") do
|
71
|
+
append(expression[:s1])
|
72
|
+
append(expression[:s2], :strip_paren => true)
|
73
|
+
append(expression[:s3], :strip_paren => true)
|
74
|
+
end
|
75
|
+
when :matrix
|
76
|
+
mrow do
|
77
|
+
mo(expression[:lparen]) if expression[:lparen]
|
78
|
+
mtable do
|
79
|
+
expression[:rows].each do |row|
|
80
|
+
mtr do
|
81
|
+
row.each do |col|
|
82
|
+
mtd do
|
83
|
+
append(col)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
mo(expression[:rparen]) if expression[:rparen]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def method_missing(meth, *args, &block)
|
96
|
+
tag(meth, *args, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def tag(tag, *args)
|
100
|
+
attrs = args.last.is_a?(Hash) ? args.pop : {}
|
101
|
+
text = args.last.is_a?(String) ? args.pop : ''
|
102
|
+
|
103
|
+
@mathml << '<' << @prefix << tag.to_s
|
104
|
+
|
105
|
+
attrs.each_pair do |key, value|
|
106
|
+
@mathml << ' ' << key << '="' << value << '"'
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
if block_given? || text
|
111
|
+
@mathml << '>'
|
112
|
+
@mathml << text
|
113
|
+
yield self if block_given?
|
114
|
+
@mathml << '</' << @prefix << tag.to_s << '>'
|
115
|
+
else
|
116
|
+
@mathml << '/>'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Expression
|
122
|
+
def to_mathml(prefix = "", attrs = {})
|
123
|
+
MathMLBuilder.new(prefix).append_expression(@parsed_expression, attrs).to_s
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,529 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
# Parser for ASCIIMath expressions.
|
4
|
+
#
|
5
|
+
# The syntax for ASCIIMath in EBNF style notation is
|
6
|
+
#
|
7
|
+
# expr = ( simp ( fraction | sub | super ) )+
|
8
|
+
# simp = constant | paren_expr | unary_expr | binary_expr | text
|
9
|
+
# fraction = '/' simp
|
10
|
+
# super = '^' simp
|
11
|
+
# sub = '_' simp super?
|
12
|
+
# paren_expr = lparen expr rparen
|
13
|
+
# lparen = '(' | '[' | '{' | '(:' | '{:'
|
14
|
+
# rparen = ')' | ']' | '}' | ':)' | ':}'
|
15
|
+
# unary_expr = unary_op simp
|
16
|
+
# unary_op = 'sqrt' | 'text'
|
17
|
+
# binary_expr = binary_op simp simp
|
18
|
+
# binary_op = 'frac' | 'root' | 'stackrel'
|
19
|
+
# text = '"' [^"]* '"'
|
20
|
+
# constant = number | symbol | identifier
|
21
|
+
# number = '-'? [0-9]+ ( '.' [0-9]+ )?
|
22
|
+
# symbol = /* any string in the symbol table */
|
23
|
+
# identifier = [A-z]
|
24
|
+
#
|
25
|
+
# ASCIIMath is parsed left to right without any form of operator precedence.
|
26
|
+
# When parsing the 'constant' the parser will try to find the longest matching string in the symbol
|
27
|
+
# table starting at the current position of the parser. If no matching string can be found the
|
28
|
+
# character at the current position of the parser is interpreted as an identifier instead.
|
29
|
+
module Asciimath
|
30
|
+
# Internal: Splits an ASCIIMath expression into a sequence of tokens.
|
31
|
+
# Each token is represented as a Hash containing the keys :value and :type.
|
32
|
+
# The :value key is used to store the text associated with each token.
|
33
|
+
# The :type key indicates the semantics of the token. The value for :type will be one
|
34
|
+
# of the following symbols:
|
35
|
+
#
|
36
|
+
# - :identifier a symbolic name or a bit of text without any further semantics
|
37
|
+
# - :text a bit of arbitrary text
|
38
|
+
# - :number a number
|
39
|
+
# - :operator a mathematical operator symbol
|
40
|
+
# - :unary a unary operator (e.g., sqrt, text, ...)
|
41
|
+
# - :infix an infix operator (e.g, /, _, ^, ...)
|
42
|
+
# - :binary a binary operator (e.g., frac, root, ...)
|
43
|
+
# - :accent an accent character
|
44
|
+
# - :eof indicates no more tokens are available
|
45
|
+
#
|
46
|
+
# Each token type may also have an :underover modifier. When present and set to true
|
47
|
+
# sub- and superscript expressions associated with the token will be rendered as
|
48
|
+
# under- and overscriptabove and below rather than as sub- or superscript.
|
49
|
+
#
|
50
|
+
# :accent tokens additionally have a :postion value which is set to either :over or :under.
|
51
|
+
# This determines if the accent should be rendered over or under the expression to which
|
52
|
+
# it applies.
|
53
|
+
#
|
54
|
+
class Tokenizer
|
55
|
+
WHITESPACE = /^\s+/
|
56
|
+
NUMBER_START = /[-0-9]/
|
57
|
+
NUMBER = /-?[0-9]+(?:\.[0-9]+)?/
|
58
|
+
TEXT = /"[^"]+"/
|
59
|
+
|
60
|
+
# Public: Initializes an ASCIIMath tokenizer.
|
61
|
+
#
|
62
|
+
# string - The ASCIIMath expression to tokenize
|
63
|
+
# symbols - The symbol table to use while tokenizing
|
64
|
+
def initialize(string, symbols)
|
65
|
+
@string = StringScanner.new(string)
|
66
|
+
@symbols = symbols
|
67
|
+
lookahead = @symbols.keys.map { |k| k.length }.max
|
68
|
+
@symbol_regexp = /([^\s0-9]{1,#{lookahead}})/
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Read the next token from the ASCIIMath expression and move the tokenizer
|
72
|
+
# ahead by one token.
|
73
|
+
#
|
74
|
+
# Returns the next token as a Hash
|
75
|
+
def next_token
|
76
|
+
if @push_back
|
77
|
+
t = @push_back
|
78
|
+
@push_back = nil
|
79
|
+
return t
|
80
|
+
end
|
81
|
+
|
82
|
+
@string.scan(WHITESPACE)
|
83
|
+
|
84
|
+
return {:value => nil, :type => :eof} if @string.eos?
|
85
|
+
|
86
|
+
case @string.peek(1)
|
87
|
+
when '"'
|
88
|
+
read_text
|
89
|
+
when NUMBER_START
|
90
|
+
read_number() || read_symbol
|
91
|
+
else
|
92
|
+
read_symbol()
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Pushes the given token back to the tokenizer. A subsequent call to next_token
|
97
|
+
# will return the given token rather than generating a new one. At most one
|
98
|
+
# token can be pushed back.
|
99
|
+
#
|
100
|
+
# token - The token to push back
|
101
|
+
def push_back(token)
|
102
|
+
@push_back = token unless token[:type] == :eof
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Private: Reads a text token from the input string
|
108
|
+
#
|
109
|
+
# Returns the text token or nil if a text token could not be matched at
|
110
|
+
# the current position
|
111
|
+
def read_text
|
112
|
+
read_value(TEXT) do |text|
|
113
|
+
{:value => text[1..-2], :type => :text}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Private: Reads a number token from the input string
|
118
|
+
#
|
119
|
+
# Returns the number token or nil if a number token could not be matched at
|
120
|
+
# the current position
|
121
|
+
def read_number
|
122
|
+
read_value(NUMBER) do |number|
|
123
|
+
{:value => number, :type => :number}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Private: Reads a symbol token from the input string. This method first creates
|
128
|
+
# a String from the input String starting from the current position with a length
|
129
|
+
# that matches that of the longest key in the symbol table. It then looks up that
|
130
|
+
# substring in the symbol table. If the substring is present in the symbol table, the
|
131
|
+
# associated value is returned and the position is moved ahead by the length of the
|
132
|
+
# substring. Otherwise this method chops one character off the end of the substring
|
133
|
+
# and repeats the symbol lookup. This continues until a single character is left.
|
134
|
+
# If that character can still not be found in the symbol table, then an identifier
|
135
|
+
# token is returned whose value is the remaining single character string.
|
136
|
+
#
|
137
|
+
# Returns the token that was read or nil if a token could not be matched at
|
138
|
+
# the current position
|
139
|
+
def read_symbol
|
140
|
+
position = @string.pos
|
141
|
+
read_value(@symbol_regexp) do |s|
|
142
|
+
until s.length == 1 || @symbols.include?(s)
|
143
|
+
s.chop!
|
144
|
+
end
|
145
|
+
@string.pos = position + s.length
|
146
|
+
@symbols[s] || {:value => s, :type => :identifier}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Private: Reads a String from the input String that matches the given RegExp
|
151
|
+
#
|
152
|
+
# regexp - a RegExp that will be used to match the token
|
153
|
+
# block - if a block is provided the matched token will be passed to the block
|
154
|
+
#
|
155
|
+
# Returns the matched String or the value returned by the block if one was given
|
156
|
+
def read_value(regexp)
|
157
|
+
s = @string.scan(regexp)
|
158
|
+
if s
|
159
|
+
yield s
|
160
|
+
else
|
161
|
+
s
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Parser
|
167
|
+
SYMBOLS = {
|
168
|
+
# Operation symbols
|
169
|
+
'+' => {:value => '+', :type => :operator},
|
170
|
+
'-' => {:value => '−', :type => :operator},
|
171
|
+
'*' => {:value => '⋅', :type => :operator},
|
172
|
+
'**' => {:value => '⋆', :type => :operator},
|
173
|
+
'//' => {:value => '/', :type => :operator},
|
174
|
+
'\\\\' => {:value => '\\', :type => :operator},
|
175
|
+
'xx' => {:value => '×', :type => :operator},
|
176
|
+
'-:' => {:value => '÷', :type => :operator},
|
177
|
+
'@' => {:value => '⚬', :type => :operator},
|
178
|
+
'o+' => {:value => '⊕', :type => :operator},
|
179
|
+
'ox' => {:value => '⊗', :type => :operator},
|
180
|
+
'o.' => {:value => '⊙', :type => :operator},
|
181
|
+
'sum' => {:value => '∑', :type => :operator, :underover => true},
|
182
|
+
'prod' => {:value => '∏', :type => :operator, :underover => true},
|
183
|
+
'^^' => {:value => '∧', :type => :operator},
|
184
|
+
'^^^' => {:value => '⋀', :type => :operator, :underover => true},
|
185
|
+
'vv' => {:value => '∨', :type => :operator},
|
186
|
+
'vvv' => {:value => '⋁', :type => :operator, :underover => true},
|
187
|
+
'nn' => {:value => '∩', :type => :operator},
|
188
|
+
'nnn' => {:value => '⋂', :type => :operator, :underover => true},
|
189
|
+
'uu' => {:value => '∪', :type => :operator},
|
190
|
+
'uuu' => {:value => '⋃', :type => :operator, :underover => true},
|
191
|
+
|
192
|
+
# Relation symbols
|
193
|
+
'=' => {:value => '=', :type => :operator},
|
194
|
+
'!=' => {:value => '≠', :type => :operator},
|
195
|
+
':=' => {:value => ':=', :type => :operator},
|
196
|
+
'<' => {:value => '<', :type => :operator},
|
197
|
+
'lt' => {:value => '<', :type => :operator},
|
198
|
+
'>' => {:value => '>', :type => :operator},
|
199
|
+
'gt' => {:value => '>', :type => :operator},
|
200
|
+
'<=' => {:value => '≤', :type => :operator},
|
201
|
+
'lt=' => {:value => '≤', :type => :operator},
|
202
|
+
'>=' => {:value => '≥', :type => :operator},
|
203
|
+
'geq' => {:value => '≥', :type => :operator},
|
204
|
+
'-<' => {:value => '≺', :type => :operator},
|
205
|
+
'-lt' => {:value => '≺', :type => :operator},
|
206
|
+
'>-' => {:value => '≻', :type => :operator},
|
207
|
+
'-<=' => {:value => '⪯', :type => :operator},
|
208
|
+
'>-=' => {:value => '⪰', :type => :operator},
|
209
|
+
'in' => {:value => '∈', :type => :operator},
|
210
|
+
'!in' => {:value => '∉', :type => :operator},
|
211
|
+
'sub' => {:value => '⊂', :type => :operator},
|
212
|
+
'sup' => {:value => '⊃', :type => :operator},
|
213
|
+
'sube' => {:value => '⊆', :type => :operator},
|
214
|
+
'supe' => {:value => '⊇', :type => :operator},
|
215
|
+
'-=' => {:value => '≡', :type => :operator},
|
216
|
+
'~=' => {:value => '≅', :type => :operator},
|
217
|
+
'~~' => {:value => '≈', :type => :operator},
|
218
|
+
'prop' => {:value => '∝', :type => :operator},
|
219
|
+
|
220
|
+
# Logical symbols
|
221
|
+
'and' => {:value => 'and', :type => :text},
|
222
|
+
'or' => {:value => 'or', :type => :text},
|
223
|
+
'not' => {:value => '¬', :type => :operator},
|
224
|
+
'=>' => {:value => '⇒', :type => :operator},
|
225
|
+
'if' => {:value => 'if', :type => :operator},
|
226
|
+
'<=>' => {:value => '⇔', :type => :operator},
|
227
|
+
'AA' => {:value => '∀', :type => :operator},
|
228
|
+
'EE' => {:value => '∃', :type => :operator},
|
229
|
+
'_|_' => {:value => '⊥', :type => :operator},
|
230
|
+
'TT' => {:value => '⊤', :type => :operator},
|
231
|
+
'|--' => {:value => '⊢', :type => :operator},
|
232
|
+
'|==' => {:value => '⊨', :type => :operator},
|
233
|
+
|
234
|
+
# Grouping brackets
|
235
|
+
'(' => {:value => '(', :type => :lparen},
|
236
|
+
')' => {:value => ')', :type => :rparen},
|
237
|
+
'[' => {:value => '[', :type => :lparen},
|
238
|
+
']' => {:value => ']', :type => :rparen},
|
239
|
+
'{' => {:value => '{', :type => :lparen},
|
240
|
+
'}' => {:value => '}', :type => :rparen},
|
241
|
+
'|' => {:value => '|', :type => :lrparen},
|
242
|
+
'||' => {:value => '||', :type => :lrparen},
|
243
|
+
'(:' => {:value => '〈', :type => :lparen},
|
244
|
+
':)' => {:value => '〉', :type => :rparen},
|
245
|
+
'<<' => {:value => '〈', :type => :lparen},
|
246
|
+
'>>' => {:value => '〉', :type => :rparen},
|
247
|
+
'{:' => {:value => nil, :type => :lparen},
|
248
|
+
':}' => {:value => nil, :type => :rparen},
|
249
|
+
|
250
|
+
# Miscellaneous symbols
|
251
|
+
'int' => {:value => '∫', :type => :operator},
|
252
|
+
'dx' => {:value => 'dx', :type => :identifier},
|
253
|
+
'dy' => {:value => 'dy', :type => :identifier},
|
254
|
+
'dz' => {:value => 'dz', :type => :identifier},
|
255
|
+
'dt' => {:value => 'dt', :type => :identifier},
|
256
|
+
'oint' => {:value => '∮', :type => :operator},
|
257
|
+
'del' => {:value => '∂', :type => :operator},
|
258
|
+
'grad' => {:value => '∇', :type => :operator},
|
259
|
+
'+-' => {:value => '±', :type => :operator},
|
260
|
+
'O/' => {:value => '∅', :type => :operator},
|
261
|
+
'oo' => {:value => '∞', :type => :operator},
|
262
|
+
'aleph' => {:value => 'ℵ', :type => :operator},
|
263
|
+
'...' => {:value => '...', :type => :operator},
|
264
|
+
':.' => {:value => '∴', :type => :operator},
|
265
|
+
'/_' => {:value => '∠', :type => :operator},
|
266
|
+
'\\ ' => {:value => ' ', :type => :operator},
|
267
|
+
'quad' => {:value => '\u00A0\u00A0', :type => :operator},
|
268
|
+
'qquad' => {:value => '\u00A0\u00A0\u00A0\u00A0', :type => :operator},
|
269
|
+
'cdots' => {:value => '⋯', :type => :operator},
|
270
|
+
'vdots' => {:value => '⋮', :type => :operator},
|
271
|
+
'ddots' => {:value => '⋱', :type => :operator},
|
272
|
+
'diamond' => {:value => '⋄', :type => :operator},
|
273
|
+
'square' => {:value => '□', :type => :operator},
|
274
|
+
'|__' => {:value => '⌊', :type => :operator},
|
275
|
+
'__|' => {:value => '⌋', :type => :operator},
|
276
|
+
'|~' => {:value => '⌈', :type => :operator},
|
277
|
+
'~|' => {:value => '⌉', :type => :operator},
|
278
|
+
'CC' => {:value => 'ℂ', :type => :operator},
|
279
|
+
'NN' => {:value => 'ℕ', :type => :operator},
|
280
|
+
'QQ' => {:value => 'ℚ', :type => :operator},
|
281
|
+
'RR' => {:value => 'ℝ', :type => :operator},
|
282
|
+
'ZZ' => {:value => 'ℤ', :type => :operator},
|
283
|
+
'f' => {:value => 'f', :type => :identifier},
|
284
|
+
'g' => {:value => 'g', :type => :identifier},
|
285
|
+
|
286
|
+
# Standard functions
|
287
|
+
'lim' => {:value => 'lim', :type => :operator, :underover => true},
|
288
|
+
'Lim' => {:value => 'Lim', :type => :operator, :underover => true},
|
289
|
+
'sin' => {:value => 'sin', :type => :operator},
|
290
|
+
'cos' => {:value => 'cos', :type => :operator},
|
291
|
+
'tan' => {:value => 'tan', :type => :operator},
|
292
|
+
'sinh' => {:value => 'sinh', :type => :operator},
|
293
|
+
'cosh' => {:value => 'cosh', :type => :operator},
|
294
|
+
'tanh' => {:value => 'tanh', :type => :operator},
|
295
|
+
'cot' => {:value => 'cot', :type => :operator},
|
296
|
+
'sec' => {:value => 'sec', :type => :operator},
|
297
|
+
'csc' => {:value => 'csc', :type => :operator},
|
298
|
+
'log' => {:value => 'log', :type => :operator},
|
299
|
+
'ln' => {:value => 'ln', :type => :operator},
|
300
|
+
'det' => {:value => 'det', :type => :operator},
|
301
|
+
'dim' => {:value => 'dim', :type => :operator},
|
302
|
+
'mod' => {:value => 'mod', :type => :operator},
|
303
|
+
'gcd' => {:value => 'gcd', :type => :operator},
|
304
|
+
'lcm' => {:value => 'lcm', :type => :operator},
|
305
|
+
'lub' => {:value => 'lub', :type => :operator},
|
306
|
+
'glb' => {:value => 'glb', :type => :operator},
|
307
|
+
'min' => {:value => 'min', :type => :operator, :underover => true},
|
308
|
+
'max' => {:value => 'max', :type => :operator, :underover => true},
|
309
|
+
|
310
|
+
# Accents
|
311
|
+
'hat' => {:value => '^', :type => :accent, :position => :over},
|
312
|
+
'bar' => {:value => '¯', :type => :accent, :position => :over},
|
313
|
+
'ul' => {:value => '_', :type => :accent, :position => :under},
|
314
|
+
'vec' => {:value => '→', :type => :accent, :position => :over},
|
315
|
+
'dot' => {:value => '.', :type => :accent, :position => :over},
|
316
|
+
'ddot' => {:value => '..', :type => :accent, :position => :over},
|
317
|
+
|
318
|
+
# Arrows
|
319
|
+
'uarr' => {:value => '↑', :type => :operator},
|
320
|
+
'darr' => {:value => '↓', :type => :operator},
|
321
|
+
'rarr' => {:value => '→', :type => :operator},
|
322
|
+
'->' => {:value => '→', :type => :operator},
|
323
|
+
'>->' => {:value => '↣', :type => :operator},
|
324
|
+
'->>' => {:value => '↠', :type => :operator},
|
325
|
+
'>->>' => {:value => '⤖', :type => :operator},
|
326
|
+
'|->' => {:value => '↦', :type => :operator},
|
327
|
+
'larr' => {:value => '←', :type => :operator},
|
328
|
+
'harr' => {:value => '↔', :type => :operator},
|
329
|
+
'rArr' => {:value => '⇒', :type => :operator},
|
330
|
+
'lArr' => {:value => '⇐', :type => :operator},
|
331
|
+
'hArr' => {:value => '⇔', :type => :operator},
|
332
|
+
|
333
|
+
# Other
|
334
|
+
'sqrt' => {:value => :sqrt, :type => :unary},
|
335
|
+
'text' => {:value => :text, :type => :unary},
|
336
|
+
'frac' => {:value => :frac, :type => :binary},
|
337
|
+
'root' => {:value => :root, :type => :binary},
|
338
|
+
'stackrel' => {:value => :over, :type => :binary},
|
339
|
+
'/' => {:value => :frac, :type => :infix},
|
340
|
+
'_' => {:value => :sub, :type => :infix},
|
341
|
+
'^' => {:value => :sup, :type => :infix},
|
342
|
+
|
343
|
+
# Greek letters
|
344
|
+
'alpha' => {:value => 'α', :type => :identifier},
|
345
|
+
'Alpha' => {:value => 'Α', :type => :identifier},
|
346
|
+
'beta' => {:value => 'β', :type => :identifier},
|
347
|
+
'Beta' => {:value => 'Β', :type => :identifier},
|
348
|
+
'gamma' => {:value => 'γ', :type => :identifier},
|
349
|
+
'Gamma' => {:value => 'Γ', :type => :operator},
|
350
|
+
'delta' => {:value => 'δ', :type => :identifier},
|
351
|
+
'Delta' => {:value => 'Δ', :type => :operator},
|
352
|
+
'epsilon' => {:value => 'ε', :type => :identifier},
|
353
|
+
'Epsilon' => {:value => 'Ε', :type => :identifier},
|
354
|
+
'varepsilon' => {:value => 'ɛ', :type => :identifier},
|
355
|
+
'zeta' => {:value => 'ζ', :type => :identifier},
|
356
|
+
'Zeta' => {:value => 'Ζ', :type => :identifier},
|
357
|
+
'eta' => {:value => 'η', :type => :identifier},
|
358
|
+
'Eta' => {:value => 'Η', :type => :identifier},
|
359
|
+
'theta' => {:value => 'θ', :type => :identifier},
|
360
|
+
'Theta' => {:value => 'Θ', :type => :operator},
|
361
|
+
'vartheta' => {:value => 'ϑ', :type => :identifier},
|
362
|
+
'iota' => {:value => 'ι', :type => :identifier},
|
363
|
+
'Iota' => {:value => 'Ι', :type => :identifier},
|
364
|
+
'kappa' => {:value => 'κ', :type => :identifier},
|
365
|
+
'Kappa' => {:value => 'Κ', :type => :identifier},
|
366
|
+
'lambda' => {:value => 'λ', :type => :identifier},
|
367
|
+
'Lambda' => {:value => 'Λ', :type => :operator},
|
368
|
+
'mu' => {:value => 'μ', :type => :identifier},
|
369
|
+
'Mu' => {:value => 'Μ', :type => :identifier},
|
370
|
+
'nu' => {:value => 'ν', :type => :identifier},
|
371
|
+
'Nu' => {:value => 'Ν', :type => :identifier},
|
372
|
+
'xi' => {:value => 'ξ', :type => :identifier},
|
373
|
+
'Xi' => {:value => 'Ξ', :type => :operator},
|
374
|
+
'omicron' => {:value => 'ο', :type => :identifier},
|
375
|
+
'Omicron' => {:value => 'Ο', :type => :identifier},
|
376
|
+
'pi' => {:value => 'π', :type => :identifier},
|
377
|
+
'Pi' => {:value => 'Π', :type => :operator},
|
378
|
+
'rho' => {:value => 'ρ', :type => :identifier},
|
379
|
+
'Rho' => {:value => 'Ρ', :type => :identifier},
|
380
|
+
'sigma' => {:value => 'σ', :type => :identifier},
|
381
|
+
'Sigma' => {:value => 'Σ', :type => :operator},
|
382
|
+
'tau' => {:value => 'τ', :type => :identifier},
|
383
|
+
'Tau' => {:value => 'Τ', :type => :identifier},
|
384
|
+
'upsilon' => {:value => 'υ', :type => :identifier},
|
385
|
+
'Upsilon' => {:value => 'Υ', :type => :identifier},
|
386
|
+
'phi' => {:value => 'φ', :type => :identifier},
|
387
|
+
'Phi' => {:value => 'Φ', :type => :identifier},
|
388
|
+
'varphi' => {:value => 'ϕ', :type => :identifier},
|
389
|
+
'chi' => {:value => '\u03b3c7', :type => :identifier},
|
390
|
+
'Chi' => {:value => '\u0393a7', :type => :identifier},
|
391
|
+
'psi' => {:value => 'ψ', :type => :identifier},
|
392
|
+
'Psi' => {:value => 'Ψ', :type => :identifier},
|
393
|
+
'omega' => {:value => 'ω', :type => :identifier},
|
394
|
+
'Omega' => {:value => 'Ω', :type => :operator},
|
395
|
+
}
|
396
|
+
|
397
|
+
def parse(input)
|
398
|
+
Expression.new(
|
399
|
+
input,
|
400
|
+
parse_expression(Tokenizer.new(input, SYMBOLS), 0)
|
401
|
+
)
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
def parse_expression(tok, depth)
|
406
|
+
e = []
|
407
|
+
|
408
|
+
while (s1 = parse_simple_expression(tok, depth))
|
409
|
+
t1 = tok.next_token
|
410
|
+
|
411
|
+
if t1[:type] == :infix
|
412
|
+
s2 = parse_simple_expression(tok, depth)
|
413
|
+
t2 = tok.next_token
|
414
|
+
if t1[:value] == :sub && t2[:value] == :sup
|
415
|
+
s3 = parse_simple_expression(tok, depth)
|
416
|
+
operator = s1[:underover] ? :underover : :subsup
|
417
|
+
e << {:type => :ternary, :operator => operator, :s1 => s1, :s2 => s2, :s3 => s3}
|
418
|
+
else
|
419
|
+
operator = s1[:underover] ? (t1[:value] == :sub ? :under : :over) : t1[:value]
|
420
|
+
e << {:type => :binary, :operator => operator, :s1 => s1, :s2 => s2}
|
421
|
+
tok.push_back(t2)
|
422
|
+
if (t2[:type] == :lrparen || t2[:type] == :rparen) && depth > 0
|
423
|
+
break
|
424
|
+
end
|
425
|
+
end
|
426
|
+
elsif t1[:type] == :eof
|
427
|
+
e << s1
|
428
|
+
break
|
429
|
+
else
|
430
|
+
e << s1
|
431
|
+
tok.push_back(t1)
|
432
|
+
if (t1[:type] == :lrparen || t1[:type] == :rparen) && depth > 0
|
433
|
+
break
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
e
|
439
|
+
end
|
440
|
+
|
441
|
+
def parse_simple_expression(tok, depth)
|
442
|
+
t1 = tok.next_token
|
443
|
+
|
444
|
+
case t1[:type]
|
445
|
+
when :lparen, :lrparen
|
446
|
+
t2 = tok.next_token
|
447
|
+
case t2[:type]
|
448
|
+
when :rparen, :lrparen
|
449
|
+
{:type => :paren, :e => e, :lparen => t1[:value], :rparen => t2[:value]}
|
450
|
+
else
|
451
|
+
tok.push_back(t2)
|
452
|
+
|
453
|
+
e = parse_expression(tok, depth + 1)
|
454
|
+
|
455
|
+
t2 = tok.next_token
|
456
|
+
case t2[:type]
|
457
|
+
when :rparen, :lrparen
|
458
|
+
convert_to_matrix({:type => :paren, :e => e, :lparen => t1[:value], :rparen => t2[:value]})
|
459
|
+
else
|
460
|
+
tok.push_back(t2)
|
461
|
+
{:type => :paren, :e => e, :lparen => t1[:value]}
|
462
|
+
end
|
463
|
+
end
|
464
|
+
when :accent
|
465
|
+
s = parse_simple_expression(tok, depth)
|
466
|
+
{:type => :binary, :s1 => s, :s2 => {:type => :operator, :c => t1[:value]}, :operator => t1[:position]}
|
467
|
+
when :unary
|
468
|
+
s = parse_simple_expression(tok, depth)
|
469
|
+
{:type => :unary, :s => s, :operator => t1[:value]}
|
470
|
+
when :binary
|
471
|
+
s1 = parse_simple_expression(tok, depth)
|
472
|
+
s2 = parse_simple_expression(tok, depth)
|
473
|
+
{:type => :binary, :s1 => s1, :s2 => s2, :operator => t1[:value]}
|
474
|
+
when :eof
|
475
|
+
nil
|
476
|
+
else
|
477
|
+
{:type => t1[:type], :c => t1[:value], :underover => t1[:underover]}
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
def convert_to_matrix(expression)
|
482
|
+
return expression unless matrix? expression
|
483
|
+
|
484
|
+
rows = expression[:e].select.with_index { |obj, i| i.even? }.map do |row|
|
485
|
+
row[:e].select.with_index { |obj, i| i.even? }
|
486
|
+
end
|
487
|
+
|
488
|
+
{:type => :matrix, :rows => rows, :lparen => expression[:lparen], :rparen => expression[:rparen]}
|
489
|
+
end
|
490
|
+
|
491
|
+
def matrix?(expression)
|
492
|
+
return false unless expression.is_a?(Hash) && expression[:type] == :paren
|
493
|
+
|
494
|
+
rows, separators = expression[:e].partition.with_index { |obj, i| i.even? }
|
495
|
+
|
496
|
+
rows.length > 1 &&
|
497
|
+
rows.length > separators.length &&
|
498
|
+
separators.all? { |item| item[:type] == :identifier && item[:c] == ',' } &&
|
499
|
+
(rows.all? { |item| item[:type] == :paren && item[:lparen] == '(' && item[:rparen] == ')' } ||
|
500
|
+
rows.all? { |item| item[:type] == :paren && item[:lparen] == '[' && item[:rparen] == ']' }) &&
|
501
|
+
rows.all? { |item| item[:e].length == rows[0][:e].length } &&
|
502
|
+
rows.all? { |item| matrix_cols?(item[:e]) }
|
503
|
+
end
|
504
|
+
|
505
|
+
def matrix_cols?(expression)
|
506
|
+
return false unless expression.is_a?(Array)
|
507
|
+
|
508
|
+
cols, separators = expression.partition.with_index { |obj, i| i.even? }
|
509
|
+
|
510
|
+
cols.all? { |item| item[:type] != :identifier || item[:c] != ',' } &&
|
511
|
+
separators.all? { |item| item[:type] == :identifier && item[:c] == ',' }
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
class Expression
|
516
|
+
def initialize(asciimath, parsed_expression)
|
517
|
+
@asciimath = asciimath
|
518
|
+
@parsed_expression = parsed_expression
|
519
|
+
end
|
520
|
+
|
521
|
+
def to_s
|
522
|
+
@asciimath
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
def self.parse(asciimath)
|
527
|
+
Parser.new.parse(asciimath)
|
528
|
+
end
|
529
|
+
end
|
data/test/parser_spec.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'asciimath'
|
3
|
+
|
4
|
+
TEST_CASES = {
|
5
|
+
'x+b/(2a)<+-sqrt((b^2)/(4a^2)-c/a)' =>
|
6
|
+
'<math><mi>x</mi><mo>+</mo><mfrac><mi>b</mi><mrow><mn>2</mn><mi>a</mi></mrow></mfrac><mo><</mo><mo>±</mo><msqrt><mrow><mfrac><msup><mi>b</mi><mn>2</mn></msup><mrow><mn>4</mn><msup><mi>a</mi><mn>2</mn></msup></mrow></mfrac><mo>−</mo><mfrac><mi>c</mi><mi>a</mi></mfrac></mrow></msqrt></math>',
|
7
|
+
'a^2 + b^2 = c^2' =>
|
8
|
+
'<math><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup><mo>=</mo><msup><mi>c</mi><mn>2</mn></msup></math>',
|
9
|
+
'x = (-b+-sqrt(b^2-4ac))/(2a)' =>
|
10
|
+
'<math><mi>x</mi><mo>=</mo><mfrac><mrow><mo>−</mo><mi>b</mi><mo>±</mo><msqrt><mrow><msup><mi>b</mi><mn>2</mn></msup><mn>-4</mn><mi>a</mi><mi>c</mi></mrow></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></math>',
|
11
|
+
'm = (y_2 - y_1)/(x_2 - x_1) = (Deltay)/(Deltax)' =>
|
12
|
+
'<math><mi>m</mi><mo>=</mo><mfrac><mrow><msub><mi>y</mi><mn>2</mn></msub><mo>−</mo><msub><mi>y</mi><mn>1</mn></msub></mrow><mrow><msub><mi>x</mi><mn>2</mn></msub><mo>−</mo><msub><mi>x</mi><mn>1</mn></msub></mrow></mfrac><mo>=</mo><mfrac><mrow><mo>Δ</mo><mi>y</mi></mrow><mrow><mo>Δ</mo><mi>x</mi></mrow></mfrac></math>',
|
13
|
+
'f\'(x) = lim_(Deltax->0)(f(x+Deltax)-f(x))/(Deltax)' =>
|
14
|
+
'<math><mi>f</mi><mi>\'</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mo>=</mo><munder><mo>lim</mo><mrow><mo>Δ</mo><mi>x</mi><mo>→</mo><mn>0</mn></mrow></munder><mfrac><mrow><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>+</mo><mo>Δ</mo><mi>x</mi><mo>)</mo></mrow><mo>−</mo><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></mrow><mrow><mo>Δ</mo><mi>x</mi></mrow></mfrac></math>',
|
15
|
+
'd/dx [x^n] = nx^(n - 1)' =>
|
16
|
+
'<math><mfrac><mi>d</mi><mi>dx</mi></mfrac><mrow><mo>[</mo><msup><mi>x</mi><mi>n</mi></msup><mo>]</mo></mrow><mo>=</mo><mi>n</mi><msup><mi>x</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msup></math>',
|
17
|
+
'int_a^b f(x) dx = [F(x)]_a^b = F(b) - F(a)' =>
|
18
|
+
'<math><msubsup><mo>∫</mo><mi>a</mi><mi>b</mi></msubsup><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mi>dx</mi><mo>=</mo><msubsup><mrow><mo>[</mo><mi>F</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mo>]</mo></mrow><mi>a</mi><mi>b</mi></msubsup><mo>=</mo><mi>F</mi><mrow><mo>(</mo><mi>b</mi><mo>)</mo></mrow><mo>−</mo><mi>F</mi><mrow><mo>(</mo><mi>a</mi><mo>)</mo></mrow></math>',
|
19
|
+
'int_a^b f(x) dx = f(c)(b - a)' =>
|
20
|
+
'<math><msubsup><mo>∫</mo><mi>a</mi><mi>b</mi></msubsup><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mi>dx</mi><mo>=</mo><mi>f</mi><mrow><mo>(</mo><mi>c</mi><mo>)</mo></mrow><mrow><mo>(</mo><mi>b</mi><mo>−</mo><mi>a</mi><mo>)</mo></mrow></math>',
|
21
|
+
'ax^2 + bx + c = 0' =>
|
22
|
+
'<math><mi>a</mi><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mi>b</mi><mi>x</mi><mo>+</mo><mi>c</mi><mo>=</mo><mn>0</mn></math>',
|
23
|
+
'"average value"=1/(b-a) int_a^b f(x) dx' =>
|
24
|
+
'<math><mtext>average value</mtext><mo>=</mo><mfrac><mn>1</mn><mrow><mi>b</mi><mo>−</mo><mi>a</mi></mrow></mfrac><msubsup><mo>∫</mo><mi>a</mi><mi>b</mi></msubsup><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mi>dx</mi></math>',
|
25
|
+
'd/dx[int_a^x f(t) dt] = f(x)' =>
|
26
|
+
'<math><mfrac><mi>d</mi><mi>dx</mi></mfrac><mrow><mo>[</mo><msubsup><mo>∫</mo><mi>a</mi><mi>x</mi></msubsup><mi>f</mi><mrow><mo>(</mo><mi>t</mi><mo>)</mo></mrow><mi>dt</mi><mo>]</mo></mrow><mo>=</mo><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow></math>',
|
27
|
+
'hat(ab) bar(xy) ul(A) vec(v)' =>
|
28
|
+
'<math><mover><mrow><mi>a</mi><mi>b</mi></mrow><mo>^</mo></mover><mover><mrow><mi>x</mi><mi>y</mi></mrow><mo>¯</mo></mover><munder><mi>A</mi><mo>_</mo></munder><mover><mi>v</mi><mo>→</mo></mover></math>',
|
29
|
+
'z_12^34' =>
|
30
|
+
'<math><msubsup><mi>z</mi><mn>12</mn><mn>34</mn></msubsup></math>',
|
31
|
+
'lim_(x->c)(f(x)-f(c))/(x-c)' =>
|
32
|
+
'<math><munder><mo>lim</mo><mrow><mi>x</mi><mo>→</mo><mi>c</mi></mrow></munder><mfrac><mrow><mi>f</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mo>−</mo><mi>f</mi><mrow><mo>(</mo><mi>c</mi><mo>)</mo></mrow></mrow><mrow><mi>x</mi><mo>−</mo><mi>c</mi></mrow></mfrac></math>',
|
33
|
+
'int_0^(pi/2) g(x) dx' =>
|
34
|
+
'<math><msubsup><mo>∫</mo><mn>0</mn><mfrac><mi>π</mi><mn>2</mn></mfrac></msubsup><mi>g</mi><mrow><mo>(</mo><mi>x</mi><mo>)</mo></mrow><mi>dx</mi></math>',
|
35
|
+
'sum_(n=0)^oo a_n' =>
|
36
|
+
'<math><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mo>∞</mo></munderover><msub><mi>a</mi><mi>n</mi></msub></math>',
|
37
|
+
'((1,2,3),(4,5,6),(7,8,9))' =>
|
38
|
+
'<math><mrow><mo>(</mo><mtable><mtr><mtd><mn>1</mn></mtd><mtd><mn>2</mn></mtd><mtd><mn>3</mn></mtd></mtr><mtr><mtd><mn>4</mn></mtd><mtd><mn>5</mn></mtd><mtd><mn>6</mn></mtd></mtr><mtr><mtd><mn>7</mn></mtd><mtd><mn>8</mn></mtd><mtd><mn>9</mn></mtd></mtr></mtable><mo>)</mo></mrow></math>',
|
39
|
+
'|(a,b),(c,d)|=ad-bc' =>
|
40
|
+
'<math><mrow><mo>|</mo><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd><mtd><mi>d</mi></mtd></mtr></mtable><mo>|</mo></mrow><mo>=</mo><mi>a</mi><mi>d</mi><mo>−</mo><mi>b</mi><mi>c</mi></math>',
|
41
|
+
'((a_(11), cdots , a_(1n)),(vdots, ddots, vdots),(a_(m1), cdots , a_(mn)))' =>
|
42
|
+
'<math><mrow><mo>(</mo><mtable><mtr><mtd><msub><mi>a</mi><mn>11</mn></msub></mtd><mtd><mo>⋯</mo></mtd><mtd><msub><mi>a</mi><mrow><mn>1</mn><mi>n</mi></mrow></msub></mtd></mtr><mtr><mtd><mo>⋮</mo></mtd><mtd><mo>⋱</mo></mtd><mtd><mo>⋮</mo></mtd></mtr><mtr><mtd><msub><mi>a</mi><mrow><mi>m</mi><mn>1</mn></mrow></msub></mtd><mtd><mo>⋯</mo></mtd><mtd><msub><mi>a</mi><mrow><mi>m</mi><mi>n</mi></mrow></msub></mtd></mtr></mtable><mo>)</mo></mrow></math>',
|
43
|
+
'sum_(k=1)^n k = 1+2+ cdots +n=(n(n+1))/2' =>
|
44
|
+
'<math><munderover><mo>∑</mo><mrow><mi>k</mi><mo>=</mo><mn>1</mn></mrow><mi>n</mi></munderover><mi>k</mi><mo>=</mo><mn>1</mn><mo>+</mo><mn>2</mn><mo>+</mo><mo>⋯</mo><mo>+</mo><mi>n</mi><mo>=</mo><mfrac><mrow><mi>n</mi><mrow><mo>(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo>)</mo></mrow></mrow><mn>2</mn></mfrac></math>',
|
45
|
+
}
|
46
|
+
|
47
|
+
module AsciimathHelper
|
48
|
+
def expect_mathml(asciimath, mathml)
|
49
|
+
expect(Asciimath.parse(asciimath).to_mathml).to eq(mathml)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
RSpec.configure do |c|
|
54
|
+
c.include AsciimathHelper
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "Asciimath::MathMLBuilder" do
|
58
|
+
TEST_CASES.each_pair do |asciimath, mathml|
|
59
|
+
it "should produce identical output to asciimathml.js for '#{asciimath}'" do
|
60
|
+
expect_mathml(asciimath, mathml)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should not generate mo elements for {: and :}' do
|
65
|
+
expect_mathml '{:(a,b),(c,d):}', '<math><mrow><mtable><mtr><mtd><mi>a</mi></mtd><mtd><mi>b</mi></mtd></mtr><mtr><mtd><mi>c</mi></mtd><mtd><mi>d</mi></mtd></mtr></mtable></mrow></math>'
|
66
|
+
end
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asciimath
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.preview.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pepijn Van Eeckhoudt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.1.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.1.0
|
55
|
+
description: A pure Ruby Asciimath parsing and conversion library.
|
56
|
+
email:
|
57
|
+
- pepijn@vaneeckhoudt.net
|
58
|
+
executables:
|
59
|
+
- asciimath
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- asciimath.gemspec
|
69
|
+
- bin/asciimath
|
70
|
+
- lib/asciimath.rb
|
71
|
+
- lib/asciimath/cli.rb
|
72
|
+
- lib/asciimath/mathml.rb
|
73
|
+
- lib/asciimath/parser.rb
|
74
|
+
- lib/asciimath/version.rb
|
75
|
+
- test/parser_spec.rb
|
76
|
+
homepage: ''
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.3.1
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.2.2
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Asciimath parser and converter
|
100
|
+
test_files:
|
101
|
+
- test/parser_spec.rb
|