calculus 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,12 @@ Gem::Specification.new do |s|
10
10
  s.email = ["sergey.avseyev@gmail.com"]
11
11
  s.homepage = "http://avsej.net/calculus"
12
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.}
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. Requres modern ruby 1.9.x due to because of using advanced oniguruma regex engine}
14
+
15
+ s.has_rdoc = true
16
+ s.rdoc_options = ['--main', 'README.rdoc']
17
+
18
+ s.required_ruby_version = '>= 1.9'
14
19
 
15
20
  s.rubyforge_project = "calculus"
16
21
 
@@ -4,7 +4,14 @@ require 'calculus/latex'
4
4
  require 'calculus/expression'
5
5
 
6
6
  module Calculus
7
+ # Raised when parser encounter invalid character
7
8
  class ParserError < Exception; end
9
+
10
+ # Raised when <tt>Expression</tt> detects during calculation that
11
+ # there are unbound variables presented
8
12
  class UnboundVariableError < Exception; end
13
+
14
+ # Raised when <tt>LaTeX</tt> mixin detect missing binaries for images
15
+ # rendering.
9
16
  class CommandNotFoundError < Exception; end
10
17
  end
@@ -2,15 +2,48 @@ require 'digest/sha1'
2
2
 
3
3
  module Calculus
4
4
 
5
+ # This class represent some expression and optionaly transform it to
6
+ # the postfix notation for later analysis.
7
+ #
8
+ # Expression can introduce variables which could be substituted later
9
+ #
10
+ # exp = Expression.new("x + 2 * 4")
11
+ # exp.to_s #=> "x + 2 * 4"
12
+ # exp.calculate # raises UnboundVariableError
13
+ # exp["x"] = 5
14
+ # exp.to_s #=> "5 + 2 * 4"
15
+ # exp.calculate #=> 40
16
+ #
5
17
  class Expression
6
18
  include Latex
7
19
 
20
+ # Represents unique identifier of expression. Should be changed when
21
+ # some variables are binding
8
22
  attr_reader :sha1
23
+
24
+ # Source expression string
9
25
  attr_reader :source
10
26
 
27
+ # Array with postfix notation of expression
11
28
  attr_reader :postfix_notation
12
29
  alias :rpn :postfix_notation
13
30
 
31
+ # Initialize instance with given string expression.
32
+ #
33
+ # It is possible to skip parser.
34
+ #
35
+ # # raises Calculus::ParserError: Invalid character...
36
+ # x = Expression.new("\sum_{i=1}^n \omega_i \x_i")
37
+ # # just stores source string and allows rendering to PNG
38
+ # x = Expression.new("\sum_{i=1}^n \omega_i \x_i", :parse => false)
39
+ # x.parsed? #=> false
40
+ #
41
+ # It raises ArgumentError if there are more than one equal sign
42
+ # because if you need to represent the system of equations you
43
+ # should you two instances of <tt>Expression</tt> class and no
44
+ # equals sign for just calculation.
45
+ #
46
+ # Also it initializes SHA1 fingerprint of particular expression
14
47
  def initialize(source, options = {})
15
48
  options = {:parse => true}.merge(options)
16
49
  @source = source
@@ -20,30 +53,46 @@ module Calculus
20
53
  update_sha1
21
54
  end
22
55
 
56
+ # Returns <tt>true</tt> when postfix notation has been built
23
57
  def parsed?
24
58
  !@postfix_notation.empty?
25
59
  end
26
60
 
61
+ # Returns <tt>true</tt> if there equals sign presented
62
+ def equation?
63
+ @postfix_notation.include?(:eql)
64
+ end
65
+
66
+ # Returns array of strings with variable names
27
67
  def variables
28
68
  @variables.keys
29
69
  end
30
70
 
71
+ # Returns array of variables which have nil value
31
72
  def unbound_variables
32
73
  @variables.keys.select{|k| @variables[k].nil?}
33
74
  end
34
75
 
76
+ # Getter for given variable.
77
+ # Raises an <tt>Argument</tt> exception when there no such variable.
35
78
  def [](name)
36
79
  raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name)
37
80
  @variables[name]
38
81
  end
39
82
 
83
+ # Setter for given variable.
84
+ # Raises an <tt>Argument</tt> exception when there no such variable.
40
85
  def []=(name, value)
41
86
  raise ArgumentError, "No such variable defined: #{name}" unless @variables.keys.include?(name)
42
87
  @variables[name] = value
43
88
  update_sha1
44
89
  end
45
90
 
46
- def traverse(&block)
91
+ # Perform traverse along postfix notation. Yields <tt>operation</tt>
92
+ # with <tt>left</tt> and <tt>right</tt> operands and the latest
93
+ # argument is the current state of <tt>stack</tt> (*note* you can
94
+ # amend this stack from outside)
95
+ def traverse(&block) # :yields: operation, left, right, stack
47
96
  stack = []
48
97
  @postfix_notation.each do |node|
49
98
  case node
@@ -59,6 +108,16 @@ module Calculus
59
108
  stack.pop
60
109
  end
61
110
 
111
+ # Traverse postfix notation and calculate the actual value of
112
+ # expression. Raises <tt>NotImplementedError</tt> when equation
113
+ # detected (currently it cannot solve equation) and
114
+ # <tt>UnboundVariableError</tt> if there unbound variables found.
115
+ #
116
+ # *Note* that there some rounding errors here in root operation
117
+ # because of general approach to calculate it:
118
+ #
119
+ # 1000 ** (1.0 / 3) #=> 9.999999999999998
120
+ #
62
121
  def calculate
63
122
  raise NotImplementedError, "Equation detected. This class can't calculate equations yet." if equation?
64
123
  raise UnboundVariableError, "Can't calculate. Unbound variables found: #{unbound_variables.join(', ')}" unless unbound_variables.empty?
@@ -75,10 +134,12 @@ module Calculus
75
134
  end
76
135
  end
77
136
 
78
- def equation?
79
- @postfix_notation.include?(:eql)
80
- end
81
-
137
+ # Builds abstract syntax tree (AST) as alternative expression
138
+ # notation. Return nested array where first member is an operation
139
+ # and the other operands
140
+ #
141
+ # Calculus::Expression.new("x + 2 * 4").ast #=> [:plus, "x", [:mul, 2, 4]]
142
+ #
82
143
  def abstract_syntax_tree
83
144
  traverse do |operation, left, right, stack|
84
145
  [operation, left, right]
@@ -86,6 +147,8 @@ module Calculus
86
147
  end
87
148
  alias :ast :abstract_syntax_tree
88
149
 
150
+ # Returns string representation of expression. Substitutes bound
151
+ # variables.
89
152
  def to_s
90
153
  result = source.dup
91
154
  (variables - unbound_variables).each do |var|
@@ -97,16 +160,21 @@ module Calculus
97
160
  result
98
161
  end
99
162
 
100
- def inspect
163
+ def inspect # :nodoc:
101
164
  "#<Expression:#{@sha1} postfix_notation=#{@postfix_notation.inspect} variables=#{@variables.inspect}>"
102
165
  end
103
166
 
104
167
  protected
105
168
 
169
+ # Extracts variables from postfix notation and returns <tt>Hash</tt>
170
+ # object with keys corresponding to variables and nil initial
171
+ # values.
106
172
  def extract_variables
107
173
  @postfix_notation.select{|node| node.kind_of? String}.inject({}){|h, v| h[v] = nil; h}
108
174
  end
109
175
 
176
+ # Update SHA1 fingerprint. Used during initialization and when
177
+ # variables is bounding.
110
178
  def update_sha1
111
179
  @sha1 = Digest::SHA1.hexdigest([@postfix_notation, @variables].map(&:inspect).join('-'))
112
180
  end
@@ -2,8 +2,13 @@ require 'tmpdir'
2
2
 
3
3
  module Calculus
4
4
 
5
+ # Renders expression to PNG image using <tt>latex<tt> and
6
+ # <tt>dvipng</tt>
5
7
  module Latex
6
8
 
9
+ # Basic latex template which use packages <tt>amsmath</tt> and
10
+ # <tt>amssymb</tt> from standard distributive and set off expression
11
+ # with <tt>$$</tt>.
7
12
  TEMPLATE = <<-EOT.gsub(/^\s+/, '')
8
13
  \\documentclass{article}
9
14
  \\usepackage{amsmath,amssymb}
@@ -13,6 +18,16 @@ module Calculus
13
18
  \\end{document}
14
19
  EOT
15
20
 
21
+ # Render image from source expression string. It is possible to pass
22
+ # <tt>background</tt> color (default: <tt>'White'</tt>) and
23
+ # <tt>density</tt> (default: <tt>700</tt>). See <tt>dvipng(1)</tt>
24
+ # page for details.
25
+ #
26
+ # Raises <tt>CommandNotFound</tt> exception when some tools not
27
+ # available.
28
+ #
29
+ # Returns path to images. *Note* that caller should take care about
30
+ # this file.
16
31
  def to_png(background = 'White', density = 700)
17
32
  raise CommandNotFoundError, "Required commands missing: #{missing_commands.join(', ')} in PATH. (#{ENV['PATH']})" unless missing_commands.empty?
18
33
 
@@ -29,6 +44,8 @@ module Calculus
29
44
  File.unlink("#{sha1}.dvi") if File.exists?("#{sha1}.dvi")
30
45
  end
31
46
 
47
+ # Check LaTeX toolchain availability and returns array of missing
48
+ # tools
32
49
  def missing_commands
33
50
  commands = []
34
51
  commands << "latex" unless can_run?("latex -v")
@@ -38,6 +55,7 @@ module Calculus
38
55
 
39
56
  protected
40
57
 
58
+ # Trial command and check if return code is zero
41
59
  def can_run?(command)
42
60
  `#{command} 2>&1`
43
61
  $?.exitstatus.zero?
@@ -2,15 +2,62 @@ require 'strscan'
2
2
 
3
3
  module Calculus
4
4
 
5
+ # Parses string with expression or equation and builds postfix
6
+ # notation. It supprorts following operators (ordered by precedence
7
+ # from the highest to the lowest):
8
+ #
9
+ # +:sqrt+, +:exp+:: root and exponent operations. Could be written as
10
+ # <tt>\sqrt[degree]{radix}</tt> and <tt>x^y</tt>.
11
+ # +:div+, +:mul+:: division and multiplication. There are set of
12
+ # syntaxes accepted. To make division operator you
13
+ # can use <tt>num/denum</tt> or
14
+ # <tt>\frac{num}{denum}</tt>. For multiplication
15
+ # there accepted <tt>*</tt> and also two TeX
16
+ # symbols: <tt>\cdot</tt> and <tt>\times</tt>.
17
+ # +:plus+, +:minus+:: summation and substraction. Here you can use
18
+ # plain <tt>+</tt> and <tt>-</tt>
19
+ # +:eql+:: equals sign it has the lowest priority so it to
20
+ # be calculated in last turn.
21
+ #
22
+ # Also it is possible to use parentheses for grouping. There are plain
23
+ # <tt>(</tt>, <tt>)</tt> acceptable and also <tt>\(</tt>, <tt>\)</tt>
24
+ # which are differ only for latex diplay. Parser doesn't distinguish
25
+ # these two styles so you could give expression with visually
26
+ # unbalanced parentheses (matter only for image generation. Consider
27
+ # the example:
28
+ #
29
+ # Parser.new("(2 + 3) * 4").parse #=> [2, 3, :plus, 4, :mul]
30
+ # Parser.new("(2 + 3\) * 4").parse #=> [2, 3, :plus, 4, :mul]
31
+ #
32
+ # This two examples will yield the same notation, but make issue
33
+ # during display.
34
+ #
35
+ # Numbers could be given as a floats and as a integer
36
+ #
37
+ # Parser.new("3 + 4.0 * 4.5e-10") #=> [3, 4.0, 4.5e-10, :mul, :plus]
38
+ #
39
+ # Symbols could be just alpha-numeric values with optional subscript
40
+ # index
41
+ #
42
+ # Parser.new("x_3 + y * E") #=> ["x_3", "y", "E", :mul, :plus]
43
+ #
5
44
  class Parser < StringScanner
6
45
  attr_accessor :operators
7
46
 
47
+ # Initialize parser with given source string. It could simple
48
+ # (native expression like <tt>2 + 3 * (4 / 3)</tt>, but also in TeX
49
+ # style <tt>2 + 3 \cdot \frac{4}{3}.
8
50
  def initialize(source)
9
51
  @operators = {:sqrt => 3, :exp => 3, :div => 2, :mul => 2, :plus => 1, :minus => 1, :eql => 0}
10
52
 
11
53
  super(source.dup)
12
54
  end
13
55
 
56
+ # Run parse cycle. It builds postfix notation (aka reverse polish
57
+ # notation). Returns array with operations with operands.
58
+ #
59
+ # Parser.new("2 + 3 * 4").parse #=> [2, 3, 4, :mul, :plus]
60
+ # Parser.new("(\frac{2}{3} + 3) * 4").parse #=> [2, 3, :div, 3, :plus, 4, :mul]
14
61
  def parse
15
62
  exp = []
16
63
  stack = []
@@ -37,6 +84,12 @@ module Calculus
37
84
  exp
38
85
  end
39
86
 
87
+ # Fetch next token from source string. Skips any whitespaces
88
+ # matching regexp <tt>/\s+/</tt> and returs <tt>nil</tt> at when
89
+ # meet the end of string.
90
+ #
91
+ # Raises <tt>ParseError</tt> when encounter invalid character
92
+ # sequence.
40
93
  def fetch_token
41
94
  skip(/\s+/)
42
95
  return nil if(eos?)
@@ -1,3 +1,3 @@
1
1
  module Calculus
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: calculus
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.3
5
+ version: 0.1.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Sergey Avseyev
@@ -10,11 +10,11 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-12 00:00:00 +03:00
13
+ date: 2011-05-13 00:00:00 +03:00
14
14
  default_executable:
15
15
  dependencies: []
16
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.
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. Requres modern ruby 1.9.x due to because of using advanced oniguruma regex engine
18
18
  email:
19
19
  - sergey.avseyev@gmail.com
20
20
  executables: []
@@ -42,8 +42,9 @@ homepage: http://avsej.net/calculus
42
42
  licenses: []
43
43
 
44
44
  post_install_message:
45
- rdoc_options: []
46
-
45
+ rdoc_options:
46
+ - --main
47
+ - README.rdoc
47
48
  require_paths:
48
49
  - lib
49
50
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -51,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
51
52
  requirements:
52
53
  - - ">="
53
54
  - !ruby/object:Gem::Version
54
- version: "0"
55
+ version: "1.9"
55
56
  required_rubygems_version: !ruby/object:Gem::Requirement
56
57
  none: false
57
58
  requirements: