asciimath 1.0.0.preview.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|