electr 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,8 @@ module Electr
4
4
  refine String do
5
5
 
6
6
  def numeric?
7
- self =~ /[0-9.]\Z/
7
+ # I know it isn't the best regexp I can do…
8
+ self =~ /\A-?[0-9\.,_]+\z/
8
9
  end
9
10
 
10
11
  def operator?
@@ -17,7 +18,11 @@ module Electr
17
18
 
18
19
  def value?
19
20
  # The unit part is redondant with Tokenizer.
20
- self =~ /[0-9.][A-Za-zΩ℧μ]{1,}\Z/
21
+ self =~ /[0-9.][A-Za-zΩ℧μ]{1,}\z/
22
+ end
23
+
24
+ def variable?
25
+ self =~ /\A[A-Z][0-9]+\z/
21
26
  end
22
27
 
23
28
  end
@@ -53,16 +53,22 @@ module Electr
53
53
  end
54
54
 
55
55
  def produce_next_token
56
+
56
57
  if begin_like_number?
57
58
  get_number
59
+
58
60
  elsif unary_minus?
59
61
  get_unary_minus
62
+
60
63
  elsif ONE_CHAR_OPERATORS.include?(@look_ahead)
61
64
  add_this_char
62
- elsif @look_ahead == '('
63
- add_this_char
64
- elsif @look_ahead == ')'
65
+
66
+ elsif %w( \( \) = ).include?(@look_ahead)
65
67
  add_this_char
68
+
69
+ elsif variable?
70
+ get_variable
71
+
66
72
  else
67
73
  get_word
68
74
  end
@@ -84,6 +90,10 @@ module Electr
84
90
  @look_ahead == '-' && @codeline[@index] =~ /[a-z\(]/
85
91
  end
86
92
 
93
+ def variable?
94
+ @look_ahead =~ /[A-Z]/ && @codeline[@index] =~ /[0-9]/
95
+ end
96
+
87
97
  def get_unary_minus
88
98
  @look_ahead = UNARY_MINUS_INTERNAL_SYMBOL
89
99
  add_look_ahead
@@ -99,9 +109,15 @@ module Electr
99
109
  @token
100
110
  end
101
111
 
112
+ def get_variable
113
+ add_look_ahead
114
+ add_look_ahead while @look_ahead =~ /[0-9]/
115
+ @token
116
+ end
117
+
102
118
  def get_word
103
119
  add_look_ahead while @look_ahead =~ /[\w√]/
104
- # At this step, an empty token signify that we have reached an
120
+ # At this point, an empty token means that we have reached an
105
121
  # illegal word.
106
122
  raise(SyntaxError, @codeline) if @token.empty?
107
123
  @token
@@ -7,3 +7,4 @@ require "electr/repl/ast_reader"
7
7
  require "electr/repl/printer"
8
8
  require "electr/repl/ast_printer"
9
9
  require "electr/repl/repl"
10
+ require "electr/repl/electr_value"
@@ -0,0 +1,78 @@
1
+ module Electr
2
+
3
+ # ElectrValue is the container of an evaluation's result.
4
+ #
5
+ # The result of the evaluation step can be of three types:
6
+ #
7
+ # - number
8
+ # - error
9
+ # - hidden
10
+ #
11
+ # The type number is the most common one. It's, you know, just a number.
12
+ # It's the result of the evaluation of `2k 3`, which is the number 6000.
13
+ # An ElectrValue of type number is meant to be printed on the screen.
14
+ #
15
+ # If an evaluation raised an error, it produced an ElectrValue of type
16
+ # error. This ElectrValue holds the error message.
17
+ #
18
+ # The type hidden is just a special case of a number. It is produced as
19
+ # the result of the evaluation of an assignment. That is, `R1 = 100`
20
+ # produces an ElectrValue of type hidden. It holds the number 100 but is
21
+ # not likely to be printed on the screen, thus the type hidden.
22
+ #
23
+ # Creating a new ElectrValue is done using class constructors:
24
+ #
25
+ # ElectrValue.number(100.0)
26
+ # ElectrValue.hidden(200.0)
27
+ # ElectrValue.error("Bad syntax error!")
28
+ #
29
+ # The class has dedicated method to check the various types:
30
+ #
31
+ # val = ElectrValue.number(100.0)
32
+ # val.number? #=> true
33
+ # val.hidden? #=> false
34
+ # val.error? #=> false
35
+ class ElectrValue
36
+
37
+ def self.number(number)
38
+ new(number)
39
+ end
40
+
41
+ def self.error(message)
42
+ new(0, :error, message)
43
+ end
44
+
45
+ def self.hidden(number)
46
+ new(number, :hidden)
47
+ end
48
+
49
+ def initialize(number, type = :number, message = "")
50
+ @number = number
51
+ @type = type
52
+ @error = message
53
+ end
54
+ private_class_method :new
55
+
56
+ attr_reader :number, :type, :error
57
+
58
+ def number?
59
+ @type == :number
60
+ end
61
+
62
+ def error?
63
+ @type == :error
64
+ end
65
+
66
+ def hidden?
67
+ @type == :hidden
68
+ end
69
+
70
+ # Say it's equal only if all three fields are equals. This is enough
71
+ # for now and there is no needs to check equality type by type.
72
+ def ==(other)
73
+ return false unless other.is_a?(ElectrValue)
74
+ @number == other.number && @type == other.type && @error = other.error
75
+ end
76
+
77
+ end
78
+ end
@@ -5,24 +5,92 @@ module Electr
5
5
 
6
6
  def initialize
7
7
  @stack = []
8
+ @environment = {}
8
9
  end
9
10
 
11
+ # The environment is where we record the variable's names and
12
+ # values.
13
+ #
14
+ # Useful for testing purposes, for now there is no other needs to
15
+ # expose this variable.
16
+ attr_reader :environment
17
+
18
+ # Do the evaluation.This method must return something meaningful for
19
+ # the Printer object. So returning nil isn't an option here.
20
+ #
21
+ # list - A list of Pn item (Prefix notation).
22
+ #
23
+ # Returns the ElectrValue result of the evaluation.
24
+ #
25
+ # See also Printer and Pn.
10
26
  def evaluate_pn(list)
27
+
11
28
  while item = list.pop
29
+
12
30
  case item.name
13
- when "numeric" then @stack.push(item.to_f)
31
+ when "numeric" then @stack.push(item.to_f)
32
+ when "variable" then @stack.push(item.value)
14
33
  when "constant" then @stack.push(constant(item.value))
15
- when "value" then do_value(item.value)
34
+ when "value" then do_value(item.value)
16
35
  when "operator" then operation(item.value)
17
36
  when "funcname" then EvalFunction.eval(item.value, @stack)
18
37
  end
19
38
  end
20
39
 
21
- @stack.pop
40
+ result = @stack.pop
41
+ if result.respond_to?(:hidden?)
42
+ result
43
+ else
44
+ ElectrValue.number(ensure_number(result))
45
+ end
46
+
47
+ rescue UnboundVariableError => e
48
+ @stack.clear
49
+ ElectrValue.error("Error: unbound variable #{e.message}")
50
+
51
+ rescue NilEvaluationError
52
+ @stack.clear
53
+ ElectrValue.error("Error: nil evaluation")
54
+
22
55
  end
23
56
 
24
57
  private
25
58
 
59
+ # In case we got the name of a variable, replace it by its value.
60
+ #
61
+ # wannabe_number - An object that can be coerced to a number. Either
62
+ # a Numeric number, the String name of a variable,
63
+ # or an ElectrValue.
64
+ #
65
+ # Returns a Numeric number.
66
+ def ensure_number(wannabe_number)
67
+
68
+ wannabe_number or raise(NilEvaluationError)
69
+
70
+ if wannabe_number.is_a?(String)
71
+ get_value_from_variable(wannabe_number)
72
+
73
+ elsif wannabe_number.respond_to?(:number)
74
+ wannabe_number.number
75
+
76
+ else
77
+ wannabe_number
78
+ end
79
+ end
80
+
81
+ # Get the value of a variable stored in the environment.
82
+ #
83
+ # name - The String name of the variable.
84
+ #
85
+ # Returns the Numeric value of the variable.
86
+ #
87
+ # Raises UnboundVariableError if the variable don't exist in the
88
+ # environment.
89
+ def get_value_from_variable(name)
90
+ variable = @environment[name]
91
+ variable ? variable.number : raise(UnboundVariableError, name)
92
+ end
93
+
26
94
  def constant(value)
27
95
  case value
28
96
  when "pi" then Math::PI
@@ -30,22 +98,34 @@ module Electr
30
98
  end
31
99
 
32
100
  def operation(operand)
33
- if operand == UNARY_MINUS_INTERNAL_SYMBOL
34
- unary_minus
101
+ case operand
102
+ when UNARY_MINUS_INTERNAL_SYMBOL then unary_minus
103
+ when "=" then assign
35
104
  else
36
105
  classic_operation(operand)
37
106
  end
38
107
  end
39
108
 
109
+ def assign
110
+ variable = @stack.pop
111
+ value = ensure_number(@stack.pop)
112
+ @environment[variable] = ElectrValue.number(value)
113
+ @stack.push(ElectrValue.hidden(value))
114
+ end
115
+
40
116
  def unary_minus
41
117
  a = @stack.pop
42
118
  @stack.push(-a)
43
119
  end
44
120
 
45
121
  def classic_operation(operand)
122
+
123
+ # Use the native ruby operand for exponentiation.
46
124
  operand = "**" if operand == '^'
47
- a = @stack.pop
48
- b = @stack.pop
125
+
126
+ a = ensure_number(@stack.pop)
127
+ b = ensure_number(@stack.pop)
128
+
49
129
  @stack.push(a.send(operand, b))
50
130
  end
51
131
 
@@ -54,15 +134,20 @@ module Electr
54
134
  # (ohm, volt, ampere, etc) and for the prefix ([m]illi, [k]ilo,
55
135
  # etc).
56
136
  val = str.to_f
137
+
57
138
  if %w( k K ).include?(str[-1]) || %w( kΩ ).include?(str[-2..-1])
58
139
  val *= 1_000
140
+
59
141
  elsif %w( mA mV mW ).include?(str[-2..-1])
60
142
  val *= 0.001
143
+
61
144
  elsif %w( u μ ).include?(str[-1]) || %w( μF uF ).include?(str[-2..-1])
62
145
  val *= 0.000_001
146
+
63
147
  elsif str[-1] == 'p' || str[-2..-1] == 'pF'
64
148
  val *= 0.000_000_001
65
149
  end
150
+
66
151
  @stack.push(val)
67
152
  end
68
153
 
@@ -1,11 +1,34 @@
1
1
  module Electr
2
2
 
3
- # Prints the computation.
3
+ # It knows how to print the result of an evaluation.
4
4
  class Printer
5
5
 
6
+ # result - The ElectrValue result of an evaluation.
6
7
  def self.run(result)
7
- truncated = result.truncate
8
- puts result == truncated ? truncated : result.round(10)
8
+ new(result).print
9
+ end
10
+
11
+ def initialize(result)
12
+ @result = result
13
+ end
14
+
15
+ def print
16
+ if @result.number?
17
+ print_number
18
+ elsif @result.error?
19
+ print_error
20
+ end
21
+ # If hidden, print nothing.
22
+ end
23
+
24
+ def print_number
25
+ number = @result.number
26
+ truncated = number.truncate
27
+ puts number == truncated ? truncated : number.round(10)
28
+ end
29
+
30
+ def print_error
31
+ puts @result.error
9
32
  end
10
33
 
11
34
  end
@@ -1,4 +1,4 @@
1
1
  module Electr
2
- VERSION = "0.0.5"
3
- CODENAME = "Units"
2
+ VERSION = "0.0.6"
3
+ CODENAME = "Vacuist Variables"
4
4
  end
@@ -17,6 +17,8 @@ describe Compiler do
17
17
  "3 (2 + 1)",
18
18
  "2 sqrt(3)",
19
19
  "2 pi 0.5uF sqrt(11K 22K)",
20
+ "1,234 + 1_234",
21
+ "C1 = 0.1",
20
22
  ]
21
23
 
22
24
  specify "#compile_to_ast" do
@@ -26,9 +28,38 @@ describe Compiler do
26
28
  end
27
29
  end
28
30
 
29
- specify "#compile_to_pn" do
30
- result = Compiler.compile_to_pn("2 pi")
31
- expect(result).to be_a Array
31
+ describe "number" do
32
+
33
+ it "allows , and _" do
34
+ result = Compiler.compile_to_pn("1,234 + 1_234")
35
+ expect(result[2].name).to eq "operator"
36
+ expect(result[2].value).to eq "+"
37
+ expect(result[3].name).to eq "numeric"
38
+ expect(result[3].value).to eq "1,234"
39
+ expect(result[4].name).to eq "numeric"
40
+ expect(result[4].value).to eq "1_234"
41
+ end
42
+ end
43
+
44
+ describe "Prefix notation" do
45
+
46
+ specify "#compile_to_pn" do
47
+ result = Compiler.compile_to_pn("2 pi")
48
+ expect(result).to be_a Array
49
+ end
50
+
51
+ specify "assignment" do
52
+ result = Compiler.compile_to_pn("R1 = 100")
53
+ expect(result[0].name).to eq "ast"
54
+ expect(result[1].name).to eq "root"
55
+ expect(result[2].value).to eq "="
56
+ expect(result[2].name).to eq "operator"
57
+ expect(result[3].value).to eq "R1"
58
+ expect(result[3].name).to eq "variable"
59
+ expect(result[4].value).to eq "100"
60
+ expect(result[4].name).to eq "numeric"
61
+ end
62
+
32
63
  end
33
64
 
34
65
  describe "Bug in the AST with `sin(pi / 2) + 1`" do
@@ -66,4 +97,32 @@ describe Compiler do
66
97
 
67
98
  end
68
99
 
100
+ # The result is as expected if one specify the multiplication symbol:
101
+ #
102
+ # R1 = 2
103
+ # R2 = 5
104
+ # R1 * R2
105
+ # #=> 10
106
+ #
107
+ # The bug shows itself when one doesn't use *:
108
+ #
109
+ # R1 = 2
110
+ # R2 = 5
111
+ # R1 R2
112
+ # #=> 2
113
+ describe "Bug with multiplication and variables" do
114
+
115
+ specify do
116
+ result = Compiler.compile_to_pn("R1 R2")
117
+
118
+ expect(result[2].value).to eq "*"
119
+ expect(result[2].name).to eq "operator"
120
+ expect(result[3].value).to eq "R1"
121
+ expect(result[3].name).to eq "variable"
122
+ expect(result[4].value).to eq "R2"
123
+ expect(result[4].name).to eq "variable"
124
+ end
125
+
126
+ end
127
+
69
128
  end
@@ -8,6 +8,9 @@ describe Lexer do
8
8
  [ :numeric, "123" ],
9
9
  [ :numeric, "0.6" ],
10
10
  [ :numeric, ".6" ],
11
+ [ :numeric, "-.6" ],
12
+ [ :numeric, "-0.6" ],
13
+ [ :numeric, "-123" ],
11
14
  [ :operator, "+" ],
12
15
  [ :operator, "-" ],
13
16
  [ :operator, "/" ],
@@ -19,6 +22,8 @@ describe Lexer do
19
22
  [ :fname, "sqrt" ],
20
23
  [ :open_parenthesis, "(" ],
21
24
  [ :closed_parenthesis, ")" ],
25
+ [ :assign, "=" ],
26
+ [ :variable, "R1" ],
22
27
  ]
23
28
 
24
29
  it "lexify" do