calculus 0.1.0
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.
- 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
|