electr 0.0.5 → 0.0.6

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.
@@ -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