electr 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.coco.yml +4 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +8 -0
  5. data/README.md +12 -13
  6. data/bin/electr +10 -5
  7. data/documentation/developer.md +21 -0
  8. data/documentation/grammar.md +34 -0
  9. data/documentation/precedence.md +8 -0
  10. data/electr.gemspec +1 -0
  11. data/lib/electr.rb +13 -2
  12. data/lib/electr/ast/ast.rb +24 -6
  13. data/lib/electr/ast/constant_ast.rb +1 -0
  14. data/lib/electr/ast/func_args_ast.rb +1 -0
  15. data/lib/electr/ast/func_ast.rb +1 -0
  16. data/lib/electr/ast/func_name_ast.rb +2 -0
  17. data/lib/electr/ast/numeric_ast.rb +1 -0
  18. data/lib/electr/ast/operator_ast.rb +1 -0
  19. data/lib/electr/ast/root_ast.rb +1 -0
  20. data/lib/electr/ast/value_ast.rb +1 -0
  21. data/lib/electr/compiler.rb +10 -6
  22. data/lib/electr/exceptions.rb +1 -0
  23. data/lib/electr/parser.rb +2 -0
  24. data/lib/electr/parser/lexer.rb +14 -37
  25. data/lib/electr/parser/lexical_unit.rb +7 -4
  26. data/lib/electr/{rules.rb → parser/rules.rb} +0 -0
  27. data/lib/electr/parser/rules/base_rule.rb +5 -13
  28. data/lib/electr/parser/rules/expression_rule.rb +18 -17
  29. data/lib/electr/parser/rules/root_rule.rb +5 -2
  30. data/lib/electr/parser/sya.rb +22 -21
  31. data/lib/electr/parser/syntaxer.rb +4 -1
  32. data/lib/electr/parser/token.rb +26 -0
  33. data/lib/electr/parser/tokenizer.rb +47 -7
  34. data/lib/electr/repl.rb +8 -56
  35. data/lib/electr/repl/ast_printer.rb +11 -0
  36. data/lib/electr/repl/ast_reader.rb +17 -0
  37. data/lib/electr/repl/base_reader.rb +23 -0
  38. data/lib/electr/{evaluator.rb → repl/evaluator.rb} +15 -1
  39. data/lib/electr/repl/nil_evaluator.rb +11 -0
  40. data/lib/electr/repl/printer.rb +12 -0
  41. data/lib/electr/repl/reader.rb +16 -0
  42. data/lib/electr/repl/repl.rb +28 -0
  43. data/lib/electr/version.rb +1 -1
  44. data/spec/ast/ast_spec.rb +17 -0
  45. data/spec/compiler_spec.rb +4 -6
  46. data/spec/parser/base_rule_spec.rb +13 -0
  47. data/spec/{lexer_spec.rb → parser/lexer_spec.rb} +5 -2
  48. data/spec/parser/pn_spec.rb +17 -0
  49. data/spec/{sya_spec.rb → parser/sya_spec.rb} +35 -0
  50. data/spec/{tokenizer_spec.rb → parser/tokenizer_spec.rb} +89 -0
  51. data/spec/repl/ast_printer_spec.rb +18 -0
  52. data/spec/repl/ast_reader_spec.rb +19 -0
  53. data/spec/{evaluator_spec.rb → repl/evaluator_spec.rb} +15 -5
  54. data/spec/repl/nil_evaluator_spec.rb +11 -0
  55. data/spec/repl/printer_spec.rb +19 -0
  56. data/spec/repl/reader_spec.rb +25 -0
  57. data/spec/repl/repl_spec.rb +32 -0
  58. data/spec/spec_helper.rb +1 -0
  59. metadata +56 -12
@@ -0,0 +1,11 @@
1
+ module Electr
2
+
3
+ # Prints the abstract syntax tree.
4
+ class ASTPrinter
5
+
6
+ def self.run(ast)
7
+ ast.display
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Electr
2
+
3
+ # Special reader for a REPL that display an AST instead of the
4
+ # computation.
5
+ class ASTReader < BaseReader
6
+
7
+ def initialize
8
+ super("E--ast> ")
9
+ end
10
+
11
+ def run
12
+ prompt
13
+ Compiler.compile_to_ast(STDIN.gets.chomp)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Electr
2
+
3
+ # Responsible of the terminal interface, that is printing the prompt
4
+ # and reading a line of code (I know, I know, there is an «and» in the
5
+ # description…).
6
+ class BaseReader
7
+
8
+ def initialize(prompt = "No prompt defined> ")
9
+ @prompt = prompt
10
+ end
11
+
12
+ def run
13
+ raise(NotImplementedError)
14
+ end
15
+
16
+ private
17
+
18
+ def prompt
19
+ print(@prompt)
20
+ end
21
+
22
+ end
23
+ end
@@ -1,5 +1,6 @@
1
1
  module Electr
2
2
 
3
+ # Evaluate (compute) a value.
3
4
  class Evaluator
4
5
 
5
6
  def initialize
@@ -9,7 +10,7 @@ module Electr
9
10
  def evaluate_pn(list)
10
11
  while item = list.pop
11
12
  case item.name
12
- when "numeric" then @stack.push(item.value.to_f)
13
+ when "numeric" then @stack.push(item.to_f)
13
14
  when "constant" then @stack.push(constant(item.value))
14
15
  when "value" then do_value(item.value)
15
16
  when "operator" then operation(item.value)
@@ -37,6 +38,19 @@ module Electr
37
38
  end
38
39
 
39
40
  def operation(operand)
41
+ if operand == UNARY_MINUS_INTERNAL_SYMBOL
42
+ unary_minus
43
+ else
44
+ classic_operation(operand)
45
+ end
46
+ end
47
+
48
+ def unary_minus
49
+ a = @stack.pop
50
+ @stack.push(-a)
51
+ end
52
+
53
+ def classic_operation(operand)
40
54
  a = @stack.pop
41
55
  b = @stack.pop
42
56
  @stack.push(a.send(operand, b))
@@ -0,0 +1,11 @@
1
+ module Electr
2
+
3
+ # An Evaluator that does nothing.
4
+ class NilEvaluator
5
+
6
+ def evaluate_pn(arg)
7
+ arg
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Electr
2
+
3
+ # Prints the computation.
4
+ class Printer
5
+
6
+ def self.run(result)
7
+ truncated = result.truncate
8
+ puts result == truncated ? truncated : result.round(10)
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Electr
2
+
3
+ # Normal reader for the REPL.
4
+ class Reader < BaseReader
5
+
6
+ def initialize
7
+ super("E> ")
8
+ end
9
+
10
+ def run
11
+ prompt
12
+ Compiler.compile_to_pn(STDIN.gets.chomp)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Electr
2
+
3
+ # The Read Eval Print Loop starting point.
4
+ class Repl
5
+
6
+ BANNER = "\nElectr version #{Electr::VERSION}\n" +
7
+ "A tiny language for electronic formulas\n\n" +
8
+ "Hit Ctrl+C to quit Electr\n\n"
9
+
10
+ def initialize(reader = Reader.new,
11
+ evaluator = Evaluator.new,
12
+ printer = Printer)
13
+ @reader = reader
14
+ @eval = evaluator
15
+ @printer = printer
16
+ end
17
+
18
+ def run
19
+ puts BANNER
20
+ run_once while true
21
+ end
22
+
23
+ def run_once
24
+ @printer.run(@eval.evaluate_pn(@reader.run))
25
+ end
26
+
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Electr
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ include Electr
4
+
5
+ describe AST do
6
+
7
+ it 'has a name' do
8
+ node = AST.new('foobar')
9
+ expect(node.name).to eq 'foobar'
10
+ end
11
+
12
+ it 'has a default name' do
13
+ node = AST.new(nil)
14
+ expect(node.name).to eq 'AST'
15
+ end
16
+
17
+ end
@@ -16,26 +16,24 @@ describe Compiler do
16
16
  "sqrt(40 + 9)",
17
17
  "3 (2 + 1)",
18
18
  "2 sqrt(3)",
19
+ "2 pi 0.5uF sqrt(11K 22K)",
19
20
  ]
20
21
 
21
22
  specify "#compile_to_ast" do
22
23
  CODES_TO_COMPILE.each do |code|
23
- compiler = Compiler.new
24
- result = compiler.compile_to_ast(code)
24
+ result = Compiler.compile_to_ast(code)
25
25
  expect(result).to be_a AST
26
26
  end
27
27
  end
28
28
 
29
29
  specify do
30
- compiler = Compiler.new
31
- result = compiler.compile_to_ast("2 pi 0.5uF sqrt(11K 22K)")
30
+ result = Compiler.compile_to_ast("-pi 2")
32
31
  result.display
33
32
  expect(result).to be_a AST
34
33
  end
35
34
 
36
35
  specify "#compile_to_pn" do
37
- compiler = Compiler.new
38
- result = compiler.compile_to_pn("2 pi")
36
+ result = Compiler.compile_to_pn("2 pi")
39
37
  expect(result).to be_a Array
40
38
  end
41
39
 
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ include Electr
4
+
5
+ describe BaseRule do
6
+
7
+ it "is not implemented" do
8
+ expect {
9
+ BaseRule.new(nil, nil).apply
10
+ }.to raise_error(NotImplementedError)
11
+ end
12
+
13
+ end
@@ -3,12 +3,15 @@ require 'spec_helper'
3
3
  include Electr
4
4
 
5
5
  describe Lexer do
6
- before { @lexer = Lexer.new }
7
6
 
8
7
  TOKENS = [
9
8
  [ :numeric, "123" ],
10
9
  [ :numeric, "0.6" ],
10
+ [ :numeric, ".6" ],
11
11
  [ :operator, "+" ],
12
+ [ :operator, "-" ],
13
+ [ :operator, "/" ],
14
+ [ :operator, UNARY_MINUS_INTERNAL_SYMBOL ],
12
15
  [ :constant, "pi" ],
13
16
  [ :value, "11K" ],
14
17
  [ :value, "11kΩ" ],
@@ -20,7 +23,7 @@ describe Lexer do
20
23
 
21
24
  it "lexify" do
22
25
  TOKENS.each do |token|
23
- lexeme = @lexer.lexify(token.last)
26
+ lexeme = Lexer.lexify(token.last)
24
27
  expect(lexeme.type).to eq token.first
25
28
  expect(lexeme.value).to eq token.last
26
29
  end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ include Electr
4
+
5
+ describe Pn do
6
+
7
+ describe '#to_f' do
8
+
9
+ it { expect(Pn.new('1000', 'n').to_f).to eq 1000.0 }
10
+
11
+ it { expect(Pn.new('1_000', 'n').to_f).to eq 1000.0 }
12
+
13
+ it { expect(Pn.new('1,000', 'n').to_f).to eq 1000.0 }
14
+
15
+ end
16
+
17
+ end
@@ -47,6 +47,17 @@ describe Sya do
47
47
  expect(sya.run).to eq [added, a, b]
48
48
  end
49
49
 
50
+ specify '2 -pi' do
51
+ a = LexicalUnit.numeric('2')
52
+ b = LexicalUnit.operator(UNARY_MINUS_INTERNAL_SYMBOL)
53
+ c = LexicalUnit.constant('pi')
54
+ added = LexicalUnit.operator('*')
55
+ sya = Sya.new([a, b, c])
56
+
57
+ # * 2 um pi
58
+ expect(sya.run).to eq [added, a, b, c]
59
+ end
60
+
50
61
  specify '2 11K' do
51
62
  a = LexicalUnit.numeric('2')
52
63
  b = LexicalUnit.constant('11K')
@@ -150,4 +161,28 @@ describe Sya do
150
161
  expect(sya.run).to eq [a, d, c, e]
151
162
  end
152
163
 
164
+ specify 'um pi' do
165
+ a = LexicalUnit.operator(UNARY_MINUS_INTERNAL_SYMBOL)
166
+ b = LexicalUnit.constant('pi')
167
+ sya = Sya.new([a, b])
168
+
169
+ # Note that the symbol used internally isn't `-`.
170
+ # - pi
171
+ expect(sya.run).to eq [a, b]
172
+ end
173
+
174
+ specify 'um sqrt(49) + 1' do
175
+ a = LexicalUnit.operator(UNARY_MINUS_INTERNAL_SYMBOL)
176
+ b = LexicalUnit.fname('sqrt')
177
+ c = LexicalUnit.open_parenthesis
178
+ d = LexicalUnit.numeric('49')
179
+ e = LexicalUnit.closed_parenthesis
180
+ f = LexicalUnit.operator('+')
181
+ g = LexicalUnit.numeric('1')
182
+ sya = Sya.new([a, b, c, d, e, f, g])
183
+
184
+ # + um sqrt 49 1
185
+ expect(sya.run).to eq [f, a, b, d, g]
186
+ end
187
+
153
188
  end
@@ -19,6 +19,33 @@ describe Tokenizer do
19
19
  end
20
20
  end
21
21
 
22
+ specify "21" do
23
+ tkr = Tokenizer.new("21")
24
+ expect(tkr.next_token).to eq "21"
25
+ end
26
+
27
+ specify "-21" do
28
+ tkr = Tokenizer.new("-21")
29
+ expect(tkr.next_token).to eq "-21"
30
+ end
31
+
32
+ specify "-.1" do
33
+ tkr = Tokenizer.new("-.1")
34
+ expect(tkr.next_token).to eq "-.1"
35
+ end
36
+
37
+ specify "- 21" do
38
+ tkr = Tokenizer.new("- 21")
39
+ expect(tkr.next_token).to eq "-"
40
+ expect(tkr.next_token).to eq "21"
41
+ end
42
+
43
+ specify "- .1" do
44
+ tkr = Tokenizer.new("- .1")
45
+ expect(tkr.next_token).to eq "-"
46
+ expect(tkr.next_token).to eq ".1"
47
+ end
48
+
22
49
  specify "21 300" do
23
50
  tkr = Tokenizer.new("21 300")
24
51
  expect(tkr.next_token).to eq "21"
@@ -31,6 +58,17 @@ describe Tokenizer do
31
58
  expect(tkr.next_token).to eq "0.6"
32
59
  end
33
60
 
61
+ specify ".6" do
62
+ tkr = Tokenizer.new(".6")
63
+ expect(tkr.next_token).to eq ".6"
64
+ end
65
+
66
+ specify "2 .6" do
67
+ tkr = Tokenizer.new("2 .6")
68
+ expect(tkr.next_token).to eq "2"
69
+ expect(tkr.next_token).to eq ".6"
70
+ end
71
+
34
72
  specify "2 0.6 3" do
35
73
  tkr = Tokenizer.new("2 0.6 3")
36
74
  expect(tkr.next_token).to eq "2"
@@ -45,12 +83,48 @@ describe Tokenizer do
45
83
  expect(tkr.next_token).to eq "3"
46
84
  end
47
85
 
86
+ specify "2 + -3" do
87
+ tkr = Tokenizer.new("2 + -3")
88
+ expect(tkr.next_token).to eq "2"
89
+ expect(tkr.next_token).to eq "+"
90
+ expect(tkr.next_token).to eq "-3"
91
+ end
92
+
48
93
  specify "2 pi" do
49
94
  tkr = Tokenizer.new("2 pi")
50
95
  expect(tkr.next_token).to eq "2"
51
96
  expect(tkr.next_token).to eq "pi"
52
97
  end
53
98
 
99
+ specify "pi" do
100
+ tkr = Tokenizer.new("pi")
101
+ expect(tkr.next_token).to eq "pi"
102
+ end
103
+
104
+ specify "-pi" do
105
+ tkr = Tokenizer.new("-pi")
106
+ expect(tkr.next_token).to eq UNARY_MINUS_INTERNAL_SYMBOL
107
+ expect(tkr.next_token).to eq "pi"
108
+ end
109
+
110
+ specify "2 -pi" do
111
+ tkr = Tokenizer.new("2 -pi")
112
+ expect(tkr.next_token).to eq "2"
113
+ expect(tkr.next_token).to eq UNARY_MINUS_INTERNAL_SYMBOL
114
+ expect(tkr.next_token).to eq "pi"
115
+ end
116
+
117
+ specify "-2V" do
118
+ tkr = Tokenizer.new("-2V")
119
+ expect(tkr.next_token).to eq "-2V"
120
+ end
121
+
122
+ specify "-sqrt(7)" do
123
+ tkr = Tokenizer.new("-sqrt(7)")
124
+ expect(tkr.next_token).to eq UNARY_MINUS_INTERNAL_SYMBOL
125
+ expect(tkr.next_token).to eq "sqrt"
126
+ end
127
+
54
128
  specify "11K 22k 33kΩ" do
55
129
  tkr = Tokenizer.new("11K 22k 33kΩ")
56
130
  expect(tkr.next_token).to eq "11K"
@@ -99,4 +173,19 @@ describe Tokenizer do
99
173
  expect(tkr.next_token).to eq ")"
100
174
  end
101
175
 
176
+ specify "-(2 3)" do
177
+ tkr = Tokenizer.new("-(2 3)")
178
+ expect(tkr.next_token).to eq UNARY_MINUS_INTERNAL_SYMBOL
179
+ expect(tkr.next_token).to eq "("
180
+ expect(tkr.next_token).to eq "2"
181
+ expect(tkr.next_token).to eq "3"
182
+ expect(tkr.next_token).to eq ")"
183
+ end
184
+
185
+ specify "22_000_000 11,000" do
186
+ tkr = Tokenizer.new("22_000_000 11,000")
187
+ expect(tkr.next_token).to eq "22_000_000"
188
+ expect(tkr.next_token).to eq "11,000"
189
+ end
190
+
102
191
  end