calculus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.rdoc +41 -0
- data/Rakefile +10 -0
- data/calculus.gemspec +21 -0
- data/lib/calculus.rb +10 -0
- data/lib/calculus/expression.rb +102 -0
- data/lib/calculus/latex.rb +47 -0
- data/lib/calculus/parser.rb +91 -0
- data/lib/calculus/version.rb +3 -0
- data/test/expression_test.rb +93 -0
- data/test/parser_test.rb +70 -0
- metadata +70 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.2@calculus --create
|
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
== Calculus
|
2
|
+
|
3
|
+
Calculus is utility library which allow to parse some subset of latex equations and store them in {Postfix notation}[http://en.wikipedia.org/wiki/Reverse_Polish_notation] It also allows translate it to {Abstract syntax tree}[http://en.wikipedia.org/wiki/Abstract_syntax_tree] and calculate (implemented for simple expressions).
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
gem install calculus
|
8
|
+
|
9
|
+
== Examples
|
10
|
+
|
11
|
+
001:0> require 'calculus'
|
12
|
+
true
|
13
|
+
002:0> exp = Calculus::Expression.new("2 + 3 * x")
|
14
|
+
#<Expression:f46e77a9377ed2d5a9da768496a7e1c20be51bfe postfix_notation=[2, 3, "x", :mul, :plus] variables={"x"=>nil}>
|
15
|
+
003:0> exp.postfix_notation
|
16
|
+
[2, 3, "x", :mul, :plus]
|
17
|
+
004:0> exp.abstract_syntax_tree
|
18
|
+
[:plus, 2, [:mul, 3, "x"]]
|
19
|
+
005:0> exp.variables
|
20
|
+
["x"]
|
21
|
+
006:0> exp.unbound_variables
|
22
|
+
["x"]
|
23
|
+
007:0> exp["x"] = 5
|
24
|
+
5
|
25
|
+
008:0> exp.unbound_variables
|
26
|
+
[]
|
27
|
+
009:0> exp.calculate
|
28
|
+
17
|
29
|
+
|
30
|
+
You can also render expression to PNG image if you have <tt>latex</tt> and <tt>dvipng</tt> installed.
|
31
|
+
|
32
|
+
010:0> Calculus::Expression.new("2 + 3 \\cdot x").to_png
|
33
|
+
"/tmp/d20110512-16457-dhxt71/f46e77a9377ed2d5a9da768496a7e1c20be51bfe.png"
|
34
|
+
|
35
|
+
{2 + 3 \cdot x}[http://files.avsej.net/expression.png]
|
36
|
+
|
37
|
+
Don't forget to cleanup file after using.
|
38
|
+
|
39
|
+
== Hacking
|
40
|
+
|
41
|
+
Just fork and pull request.
|
data/Rakefile
ADDED
data/calculus.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "calculus/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "calculus"
|
7
|
+
s.version = Calculus::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Sergey Avseyev"]
|
10
|
+
s.email = ["sergey.avseyev@gmail.com"]
|
11
|
+
s.homepage = "http://avsej.net/calculus"
|
12
|
+
s.summary = %q{A ruby parser for TeX equations}
|
13
|
+
s.description = %q{A ruby parser for TeX equations. It parses equations to postfix (reverse polish) notation and can build abstract syntax tree (AST). Also it can render images via latex.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "calculus"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
data/lib/calculus.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'calculus/version'
|
2
|
+
require 'calculus/parser'
|
3
|
+
require 'calculus/latex'
|
4
|
+
require 'calculus/expression'
|
5
|
+
|
6
|
+
module Calculus
|
7
|
+
class ParserError < Exception; end
|
8
|
+
class UnboundVariableError < Exception; end
|
9
|
+
class CommandNotFoundError < Exception; end
|
10
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Calculus
|
4
|
+
|
5
|
+
class Expression
|
6
|
+
include Latex
|
7
|
+
|
8
|
+
attr_reader :sha1
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
attr_reader :postfix_notation
|
12
|
+
alias :rpn :postfix_notation
|
13
|
+
|
14
|
+
def initialize(source)
|
15
|
+
@postfix_notation = Parser.new(@source = source).parse
|
16
|
+
@variables = extract_variables
|
17
|
+
update_sha1
|
18
|
+
end
|
19
|
+
|
20
|
+
def variables
|
21
|
+
@variables.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbound_variables
|
25
|
+
@variables.keys.select{|k| @variables[k].nil?}
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](name)
|
29
|
+
raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name)
|
30
|
+
@variables[name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(name, value)
|
34
|
+
raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name)
|
35
|
+
@variables[name] = value
|
36
|
+
update_sha1
|
37
|
+
end
|
38
|
+
|
39
|
+
def traverse(&block)
|
40
|
+
stack = []
|
41
|
+
@postfix_notation.each do |node|
|
42
|
+
case node
|
43
|
+
when Symbol
|
44
|
+
operation, right, left = node, stack.pop, stack.pop
|
45
|
+
stack.push(yield(operation, left, right, stack))
|
46
|
+
when Numeric
|
47
|
+
stack.push(node)
|
48
|
+
when String
|
49
|
+
stack.push(@variables[node] || node)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
stack.pop
|
53
|
+
end
|
54
|
+
|
55
|
+
def calculate
|
56
|
+
raise NotImplementedError, "Equation detected. This class can't calculate equations yet." if equation?
|
57
|
+
raise UnboundVariableError, "Can't calculate. Unbound variables found: #{unbound_variables.join(', ')}" unless unbound_variables.empty?
|
58
|
+
|
59
|
+
traverse do |operation, left, right, stack|
|
60
|
+
case operation
|
61
|
+
when :sqrt then left ** (1.0 / right) # could cause some rounding errors
|
62
|
+
when :exp then left ** right
|
63
|
+
when :plus then left + right
|
64
|
+
when :minus then left - right
|
65
|
+
when :mul then left * right
|
66
|
+
when :div then left / right
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def equation?
|
72
|
+
@postfix_notation.include?(:eql)
|
73
|
+
end
|
74
|
+
|
75
|
+
def abstract_syntax_tree
|
76
|
+
traverse do |operation, left, right, stack|
|
77
|
+
[operation, left, right]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias :ast :abstract_syntax_tree
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
source
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
"#<Expression:#{@sha1} postfix_notation=#{@postfix_notation.inspect} variables=#{@variables.inspect}>"
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def extract_variables
|
93
|
+
@postfix_notation.select{|node| node.kind_of? String}.inject({}){|h, v| h[v] = nil; h}
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_sha1
|
97
|
+
@sha1 = Digest::SHA1.hexdigest([@postfix_notation, @variables].map(&:inspect).join('-'))
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
module Calculus
|
4
|
+
|
5
|
+
module Latex
|
6
|
+
|
7
|
+
TEMPLATE = <<-EOT.gsub(/^\s+/, '')
|
8
|
+
\\documentclass{article}
|
9
|
+
\\usepackage{amsmath,amssymb}
|
10
|
+
\\begin{document}
|
11
|
+
\\thispagestyle{empty}
|
12
|
+
$$ # $$
|
13
|
+
\\end{document}
|
14
|
+
EOT
|
15
|
+
|
16
|
+
def to_png(density = 700)
|
17
|
+
raise CommandNotFoundError, "Required commands missing: #{missing_commands.join(', ')} in PATH. (#{ENV['PATH']})" unless missing_commands.empty?
|
18
|
+
|
19
|
+
temp_path = Dir.mktmpdir
|
20
|
+
Dir.chdir(temp_path) do
|
21
|
+
File.open("#{sha1}.tex", 'w') do |f|
|
22
|
+
f.write(TEMPLATE.sub('#', self.to_s))
|
23
|
+
end
|
24
|
+
`latex -interaction=nonstopmode #{sha1}.tex && dvipng -q -T tight -bg White -D #{density.to_i} -o #{sha1}.png #{sha1}.dvi`
|
25
|
+
end
|
26
|
+
return File.join(temp_path, "#{sha1}.png") if $?.exitstatus.zero?
|
27
|
+
ensure
|
28
|
+
File.unlink("#{sha1}.tex") if File.exists?("#{sha1}.tex")
|
29
|
+
File.unlink("#{sha1}.dvi") if File.exists?("#{sha1}.dvi")
|
30
|
+
end
|
31
|
+
|
32
|
+
def missing_commands
|
33
|
+
commands = []
|
34
|
+
commands << "latex" unless can_run?("latex -v")
|
35
|
+
commands << "dvipng" unless can_run?("dvipng -v")
|
36
|
+
commands
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def can_run?(command)
|
42
|
+
`#{command} 2>&1`
|
43
|
+
$?.exitstatus.zero?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Calculus
|
4
|
+
|
5
|
+
class Parser < StringScanner
|
6
|
+
attr_accessor :operators
|
7
|
+
|
8
|
+
def initialize(source)
|
9
|
+
@operators = {:sqrt => 3, :exp => 3, :div => 2, :mul => 2, :plus => 1, :minus => 1, :eql => 0}
|
10
|
+
|
11
|
+
super(source)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
exp = []
|
16
|
+
stack = []
|
17
|
+
while true
|
18
|
+
case token = fetch_token
|
19
|
+
when :open
|
20
|
+
stack.push(token)
|
21
|
+
when :close
|
22
|
+
exp << stack.pop while operators.keys.include?(stack.last)
|
23
|
+
stack.pop if stack.last == :open
|
24
|
+
when :plus, :minus, :mul, :div, :exp, :sqrt, :eql
|
25
|
+
exp << stack.pop while operators.keys.include?(stack.last) && operators[stack.last] >= operators[token]
|
26
|
+
stack.push(token)
|
27
|
+
when Numeric, String
|
28
|
+
exp << token
|
29
|
+
when nil
|
30
|
+
break
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Unexpected symbol: #{token.inspect}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
exp << stack.pop while stack.last && stack.last != :open
|
36
|
+
raise ArgumentError, "Missing closing parentheses: #{stack.join(', ')}" unless stack.empty?
|
37
|
+
exp
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_token
|
41
|
+
skip(/\s+/)
|
42
|
+
return nil if(eos?)
|
43
|
+
|
44
|
+
token = nil
|
45
|
+
scanning = true
|
46
|
+
while(scanning)
|
47
|
+
scanning = false
|
48
|
+
token = case
|
49
|
+
when scan(/=/)
|
50
|
+
:eql
|
51
|
+
when scan(/\*|\\times|\\cdot/)
|
52
|
+
:mul
|
53
|
+
when scan(/\\frac\s*(?<num>\{(?:(?>[^{}])|\g<num>)*\})\s*(?<denom>\{(?:(?>[^{}])|\g<denom>)*\})/)
|
54
|
+
num, denom = [self[1], self[2]].map{|v| v.gsub(/^{|}$/, '')}
|
55
|
+
string[pos, 0] = "(#{num}) / (#{denom}) "
|
56
|
+
scanning = true
|
57
|
+
when scan(/\//)
|
58
|
+
:div
|
59
|
+
when scan(/\+/)
|
60
|
+
:plus
|
61
|
+
when scan(/\^/)
|
62
|
+
:exp
|
63
|
+
when scan(/-/)
|
64
|
+
:minus
|
65
|
+
when scan(/sqrt/)
|
66
|
+
:sqrt
|
67
|
+
when scan(/\\sqrt\s*(?<deg>\[(?:(?>[^\[\]])|\g<deg>)*\])?\s*(?<rad>\{(?:(?>[^{}])|\g<rad>)*\})/)
|
68
|
+
deg = (self[1] || "2").gsub(/^\[|\]$/, '')
|
69
|
+
rad = self[2].gsub(/^{|}$/, '')
|
70
|
+
string[pos, 0] = "(#{rad}) sqrt (#{deg}) "
|
71
|
+
scanning = true
|
72
|
+
when scan(/\(|\\left\(/)
|
73
|
+
:open
|
74
|
+
when scan(/\)|\\right\)/)
|
75
|
+
:close
|
76
|
+
when scan(/[\-\+]? [0-9]+ ((e[\-\+]?[0-9]+)| (\.[0-9]+(e[\-\+]?[0-9]+)?))/x)
|
77
|
+
matched.to_f
|
78
|
+
when scan(/[\-\+]?[0-9]+/)
|
79
|
+
matched.to_i
|
80
|
+
when scan(/([a-z0-9]+(?>_[a-z0-9]+)?)/i)
|
81
|
+
matched
|
82
|
+
else
|
83
|
+
raise ParserError, "Invalid character at position #{pos} near '#{peek(20)}'."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
return token
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'calculus'
|
3
|
+
|
4
|
+
class TestExpression < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def test_that_it_extract_variables_properly
|
7
|
+
assert_equal ["x", "y"], expression("x + 2^x = y").variables
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_that_it_empty_variables_array_if_they_are_absent
|
11
|
+
assert_equal [], expression("4 + 2^3 = 12").variables
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_that_variables_can_be_read_using_square_brackets
|
15
|
+
exp = expression("4 + x^3 = 12")
|
16
|
+
exp.instance_variable_get("@variables")["x"] = 2
|
17
|
+
assert_equal 2, exp["x"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_that_variables_can_be_written_using_square_brackets
|
21
|
+
exp = expression("4 + x^3 = 12")
|
22
|
+
exp["x"] = 2
|
23
|
+
assert_equal 2, exp.instance_variable_get("@variables")["x"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_that_it_raises_exception_for_unexistent_variable
|
27
|
+
exp = expression("4 + x^3 = 12")
|
28
|
+
assert_raises(ArgumentError) { exp["y"] }
|
29
|
+
assert_raises(ArgumentError) { exp["y"] = 3 }
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_that_it_initializes_variables_with_nils
|
33
|
+
exp = expression("x + 2^x = y")
|
34
|
+
assert_nil exp["x"]
|
35
|
+
assert_nil exp["y"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_that_it_gives_access_to_postfix_notation
|
39
|
+
exp = expression("x + 2^x = y")
|
40
|
+
assert_equal ["x", 2, "x", :exp, :plus, "y", :eql], exp.postfix_notation
|
41
|
+
assert_equal ["x", 2, "x", :exp, :plus, "y", :eql], exp.rpn
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_that_it_gives_access_to_abstract_syntax_tree
|
45
|
+
exp = expression("(2 + 3) * 4")
|
46
|
+
assert_equal [:mul, [:plus, 2, 3], 4], exp.abstract_syntax_tree
|
47
|
+
assert_equal [:mul, [:plus, 2, 3], 4], exp.ast
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_that_it_gives_list_of_unbound_variables
|
51
|
+
exp = expression("x + 2^x = y")
|
52
|
+
assert_equal ["x", "y"], exp.unbound_variables
|
53
|
+
exp["x"] = 3
|
54
|
+
assert_equal ["y"], exp.unbound_variables
|
55
|
+
exp["y"] = 2
|
56
|
+
assert_equal [], exp.unbound_variables
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_that_calculate_raises_unbound_variable_error_when_some_variables_missing
|
60
|
+
assert_raises(Calculus::UnboundVariableError) { expression("x + 2 * 3").calculate }
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_that_calculate_raises_not_implemented_error_when_detects_equation
|
64
|
+
assert_raises(NotImplementedError) { expression("x + 2 = 7").calculate }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_that_it_calclulates_simple_expressions
|
68
|
+
assert_equal 8, expression("2 \\cdot 4").calculate
|
69
|
+
assert_equal 6, expression("2 + 2 * 2").calculate
|
70
|
+
assert_equal 4, expression("\\frac{4}{2} * 2").calculate
|
71
|
+
assert_equal 16, expression("4^2").calculate
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_that_it_substitutes_variables_during_calculation
|
75
|
+
exp = expression("2 + 2 * x")
|
76
|
+
exp["x"] = 2
|
77
|
+
assert_equal 6, exp.calculate
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_that_it_refresh_sha1_sub_when_variables_get_filled
|
81
|
+
exp = expression("2 \\cdot x = 4")
|
82
|
+
old_sha1 = exp.sha1
|
83
|
+
exp["x"] = 2
|
84
|
+
refute_equal old_sha1, exp.sha1
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def expression(input)
|
90
|
+
Calculus::Expression.new(input)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/test/parser_test.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'calculus'
|
3
|
+
|
4
|
+
class TestParser < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def test_that_it_parses_simple_arithmetic
|
7
|
+
assert_equal [1, 2, :plus], parse("1+2")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_that_it_skips_spaces
|
11
|
+
assert_equal [1, 2, :plus], parse("1 + 2")
|
12
|
+
assert_equal [4, 2, :exp], parse(" 4 ^ 2 ")
|
13
|
+
assert_equal [4, 2, :sqrt], parse("\\sqrt [ 2 ] { 4 }")
|
14
|
+
assert_equal [5, 4, :div], parse("\\frac { 5 } { 4 }")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_that_it_properly_parses_square_root
|
18
|
+
assert_equal [2, 4, 2, :sqrt, :mul], parse("2 * \\sqrt{4}")
|
19
|
+
assert_equal [8, 3, :sqrt], parse("\\sqrt[3]{8}")
|
20
|
+
assert_equal [8, 3, 2, :plus, :sqrt], parse("\\sqrt[3+2]{8}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_that_it_properly_parses_fractions
|
24
|
+
assert_equal [8, 3, :div], parse("\\frac{8}{3}")
|
25
|
+
assert_equal [3, 1, :plus, 3, 1, :minus, :div], parse("\\frac{3+1}{3-1}")
|
26
|
+
assert_equal [3, 1, :plus, 3, 1, :minus, 4, :mul, :div], parse("\\frac{3+1}{(3-1)*4}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_that_it_honours_priorities
|
30
|
+
assert_equal [3, 2, 2, :mul, :plus], parse("3+2*2")
|
31
|
+
assert_equal [3, 5, 4, :exp, :mul, 2, :plus], parse("3*5^4+2")
|
32
|
+
assert_equal [3, 5, :mul, 4, :exp, 2, :plus], parse("(3*5)^4+2")
|
33
|
+
assert_equal [3, 5, :mul, 2, :sqrt, 2, :plus], parse("\\sqrt{3*5}+2")
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_that_it_allows_parentesis
|
37
|
+
assert_equal [3, 2, :plus, 2, :mul], parse("(3+2)*2")
|
38
|
+
assert_equal [3, 2, :plus, 2, :mul], parse("\\left(3+2\\right)*2")
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_that_it_properly_parses_floats
|
42
|
+
assert_equal [1.2], parse("1.2")
|
43
|
+
assert_equal [1.2e10], parse("1.2e10")
|
44
|
+
assert_equal [1.2e-10], parse("1.2e-10")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_that_it_allows_nesting
|
48
|
+
assert_equal [8, 2, :sqrt, 3, :div], parse("\\frac{\\sqrt{8}}{3}")
|
49
|
+
assert_equal [4, 8, 3, :div, :sqrt], parse("\\sqrt[\\frac{8}{3}]{4}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_that_it_recognizes_equals_sign
|
53
|
+
assert_equal [2, 4, :mul, 2, :div, 16, 2, :sqrt, :eql], parse("2 \\cdot \\frac{4}{2} = \\sqrt{16}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_that_it_recognizes_variables
|
57
|
+
assert_equal [2, "x", :mul, 16, :eql], parse("2 \\cdot x = 16")
|
58
|
+
assert_equal [2, "x_i", :mul, 16, :eql], parse("2 \\cdot x_i = 16")
|
59
|
+
assert_equal [2, "x_2", :mul, 16, :eql], parse("2 \\cdot x_2 = 16")
|
60
|
+
assert_equal [2, "x2", :mul, 16, :eql], parse("2 \\cdot x2 = 16")
|
61
|
+
assert_raises(Calculus::ParserError) { assert_equal [2, "x__2", :mul, 16, :eql], parse("2 \\cdot x__2 = 16") }
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def parse(input)
|
67
|
+
Calculus::Parser.new(input).parse
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: calculus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sergey Avseyev
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-12 00:00:00 +03:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: A ruby parser for TeX equations. It parses equations to postfix (reverse polish) notation and can build abstract syntax tree (AST). Also it can render images via latex.
|
18
|
+
email:
|
19
|
+
- sergey.avseyev@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- .gitignore
|
28
|
+
- .rvmrc
|
29
|
+
- Gemfile
|
30
|
+
- README.rdoc
|
31
|
+
- Rakefile
|
32
|
+
- calculus.gemspec
|
33
|
+
- lib/calculus.rb
|
34
|
+
- lib/calculus/expression.rb
|
35
|
+
- lib/calculus/latex.rb
|
36
|
+
- lib/calculus/parser.rb
|
37
|
+
- lib/calculus/version.rb
|
38
|
+
- test/expression_test.rb
|
39
|
+
- test/parser_test.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://avsej.net/calculus
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project: calculus
|
64
|
+
rubygems_version: 1.6.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: A ruby parser for TeX equations
|
68
|
+
test_files:
|
69
|
+
- test/expression_test.rb
|
70
|
+
- test/parser_test.rb
|