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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +77 -50
- data/bin/electr +1 -5
- data/electr.gemspec +1 -1
- data/lib/electr.rb +10 -9
- data/lib/electr/ast.rb +1 -0
- data/lib/electr/ast/ast.rb +1 -0
- data/lib/electr/ast/variable_ast.rb +13 -0
- data/lib/electr/compiler.rb +0 -1
- data/lib/electr/exceptions.rb +6 -0
- data/lib/electr/option.rb +2 -2
- data/lib/electr/parser/lexer.rb +2 -0
- data/lib/electr/parser/lexical_unit.rb +9 -0
- data/lib/electr/parser/rules/expression_rule.rb +17 -2
- data/lib/electr/parser/sya.rb +70 -11
- data/lib/electr/parser/token.rb +7 -2
- data/lib/electr/parser/tokenizer.rb +20 -4
- data/lib/electr/repl.rb +1 -0
- data/lib/electr/repl/electr_value.rb +78 -0
- data/lib/electr/repl/evaluator.rb +92 -7
- data/lib/electr/repl/printer.rb +26 -3
- data/lib/electr/version.rb +2 -2
- data/spec/compiler_spec.rb +62 -3
- data/spec/parser/lexer_spec.rb +5 -0
- data/spec/parser/sya_spec.rb +64 -0
- data/spec/parser/tokenizer_spec.rb +13 -0
- data/spec/repl/electr_value_spec.rb +86 -0
- data/spec/repl/evaluator_spec.rb +112 -3
- data/spec/repl/printer_spec.rb +42 -2
- data/spec/repl/repl_spec.rb +1 -1
- metadata +7 -6
- data/documentation/developer.md +0 -21
- data/documentation/grammar.md +0 -34
- data/documentation/precedence.md +0 -10
data/lib/electr/parser/token.rb
CHANGED
@@ -4,7 +4,8 @@ module Electr
|
|
4
4
|
refine String do
|
5
5
|
|
6
6
|
def numeric?
|
7
|
-
|
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,}\
|
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
|
-
|
63
|
-
|
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
|
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
|
data/lib/electr/repl.rb
CHANGED
@@ -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"
|
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"
|
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
|
-
|
34
|
-
|
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
|
-
|
48
|
-
|
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
|
|
data/lib/electr/repl/printer.rb
CHANGED
@@ -1,11 +1,34 @@
|
|
1
1
|
module Electr
|
2
2
|
|
3
|
-
#
|
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
|
-
|
8
|
-
|
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
|
data/lib/electr/version.rb
CHANGED
data/spec/compiler_spec.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
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
|
data/spec/parser/lexer_spec.rb
CHANGED
@@ -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
|