electr 0.0.2

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +196 -0
  9. data/Rakefile +7 -0
  10. data/bin/electr +27 -0
  11. data/electr.gemspec +25 -0
  12. data/lib/electr.rb +20 -0
  13. data/lib/electr/ast.rb +9 -0
  14. data/lib/electr/ast/ast.rb +62 -0
  15. data/lib/electr/ast/constant_ast.rb +14 -0
  16. data/lib/electr/ast/func_args_ast.rb +11 -0
  17. data/lib/electr/ast/func_ast.rb +12 -0
  18. data/lib/electr/ast/func_name_ast.rb +14 -0
  19. data/lib/electr/ast/numeric_ast.rb +13 -0
  20. data/lib/electr/ast/operator_ast.rb +13 -0
  21. data/lib/electr/ast/root_ast.rb +11 -0
  22. data/lib/electr/ast/value_ast.rb +12 -0
  23. data/lib/electr/compiler.rb +24 -0
  24. data/lib/electr/evaluator.rb +64 -0
  25. data/lib/electr/exceptions.rb +6 -0
  26. data/lib/electr/option.rb +70 -0
  27. data/lib/electr/parser.rb +5 -0
  28. data/lib/electr/parser/lexer.rb +50 -0
  29. data/lib/electr/parser/lexical_unit.rb +67 -0
  30. data/lib/electr/parser/rules/base_rule.rb +60 -0
  31. data/lib/electr/parser/rules/expression_rule.rb +79 -0
  32. data/lib/electr/parser/rules/root_rule.rb +21 -0
  33. data/lib/electr/parser/sya.rb +105 -0
  34. data/lib/electr/parser/syntaxer.rb +21 -0
  35. data/lib/electr/parser/tokenizer.rb +79 -0
  36. data/lib/electr/repl.rb +56 -0
  37. data/lib/electr/rules.rb +3 -0
  38. data/lib/electr/version.rb +3 -0
  39. data/spec/compiler_spec.rb +42 -0
  40. data/spec/electr_spec.rb +7 -0
  41. data/spec/evaluator_spec.rb +47 -0
  42. data/spec/lexer_spec.rb +29 -0
  43. data/spec/spec_helper.rb +8 -0
  44. data/spec/sya_spec.rb +153 -0
  45. data/spec/tokenizer_spec.rb +102 -0
  46. metadata +138 -0
@@ -0,0 +1,79 @@
1
+ module Electr
2
+
3
+ class ExpressionRule < BaseRule
4
+
5
+ def apply!
6
+ while @units.size > 0
7
+ if first_unit_fname?
8
+ accept(:fname)
9
+ accept(:open_parenthesis, '(')
10
+ while @units.first && (not @units.first.closed_parenthesis?)
11
+ accept(@units.first.type)
12
+ end
13
+ accept(:closed_parenthesis, ')')
14
+ else
15
+ accept(@units.first.type)
16
+ end
17
+ end
18
+
19
+ sya = Sya.new(@series.dup)
20
+ @series = sya.run
21
+
22
+ if @series.first.numeric? || @series.first.constant? ||
23
+ @series.first.value? || @series.first.fname?
24
+ dig_series(@ast_node)
25
+ else
26
+ unit = @series.shift
27
+ node = OperatorAST.new(unit.value)
28
+ dig_series(node) while @series.size > 0
29
+ @ast_node.add_child(node)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def first_unit_number?
36
+ @units.first && @units.first.number?
37
+ end
38
+
39
+ def first_unit_numeric?
40
+ @units.first && @units.first.numeric?
41
+ end
42
+
43
+ def first_unit_operator?
44
+ @units.first && @units.first.operator?
45
+ end
46
+
47
+ def first_unit_fname?
48
+ @units.first && @units.first.fname?
49
+ end
50
+
51
+ def dig_series(node)
52
+ while unit = @series.shift
53
+ if unit && unit.numeric? && node.size < 2
54
+ node.add_child(NumericAST.new(unit.value))
55
+ elsif unit && unit.constant? && node.size < 2
56
+ node.add_child(ConstantAST.new(unit.value))
57
+ elsif unit && unit.value? && node.size < 2
58
+ node.add_child(ValueAST.new(unit.value))
59
+ elsif unit && unit.fname? && node.size < 2
60
+ func = FuncAST.new
61
+ func_name = FuncNameAST.new(unit.value)
62
+ func_args = FuncArgsAST.new
63
+ dig_series(func_args)
64
+ func.add_child(func_name)
65
+ func.add_child(func_args)
66
+ node.add_child(func)
67
+ elsif unit && unit.operator?
68
+ new_node = OperatorAST.new(unit.value)
69
+ dig_series(new_node)
70
+ node.add_child(new_node)
71
+ else
72
+ @series.unshift(unit)
73
+ break
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,21 @@
1
+ module Electr
2
+
3
+ class RootRule < BaseRule
4
+
5
+ def apply!
6
+ root_node = RootAST.new
7
+ while more_units?
8
+ ExpressionRule.new(@units, root_node).apply!
9
+ end
10
+ @ast_node.add_child(root_node)
11
+ end
12
+
13
+ private
14
+
15
+ def more_units?
16
+ @units.size > 0
17
+ end
18
+
19
+ end
20
+ end
21
+
@@ -0,0 +1,105 @@
1
+ module Electr
2
+
3
+ # Shunting Yard Algorithm.
4
+ #
5
+ # It produces prefix notation instead of the most common reverse
6
+ # polish notation to ease producing the AST.
7
+ class Sya
8
+
9
+ PRECEDENCE = {
10
+ '()' => {assoc: 'L', val: 99},
11
+ ')' => {assoc: 'L', val: 99},
12
+ '(' => {assoc: 'L', val: 99},
13
+ '*' => {assoc: 'L', val: 10},
14
+ '/' => {assoc: 'L', val: 10},
15
+ '-' => {assoc: 'L', val: 1},
16
+ '+' => {assoc: 'L', val: 1},
17
+ }
18
+
19
+ # lexical_units - Array of LexicalUnit in the order they have been
20
+ # parsed (natural ordering).
21
+ def initialize(lexical_units)
22
+ @units = lexical_units
23
+ insert_multiplication
24
+ @output = []
25
+ @operator = []
26
+ end
27
+
28
+ # Run the Shunting Yard Algorithm.
29
+ #
30
+ # Returns Array of LexicalUnit ordered with prefix notation.
31
+ def run
32
+ while unit = @units.pop
33
+ if unit.number?
34
+ @output.push(unit)
35
+ elsif unit.fname?
36
+ @operator.push(unit)
37
+ elsif unit.type == :closed_parenthesis
38
+ @operator.push(unit)
39
+ elsif unit.type == :open_parenthesis
40
+ while @operator.last &&
41
+ @operator.last.type != :closed_parenthesis
42
+ @output.push(@operator.pop)
43
+ end
44
+ rparen = @operator.pop
45
+ if @operator.last && @operator.last.type == :fname
46
+ @output.push(@operator.pop)
47
+ end
48
+ # If the stack runs out without finding a right parenthesis,
49
+ # then there are mismatched parentheses.
50
+ elsif unit.operator?
51
+ while @operator.size > 0 &&
52
+ (@operator.last.operator? || @operator.last.fname?) &&
53
+ (precedence(unit) < precedence(@operator.last))
54
+ @output.push(@operator.pop)
55
+ end
56
+ @operator.push(unit)
57
+ end
58
+ end
59
+
60
+ @output.push(@operator.pop) while @operator.size > 0
61
+
62
+ @output.reverse
63
+ end
64
+
65
+ private
66
+
67
+ # Look up the precedence of an operator.
68
+ #
69
+ # operator - LexicalUnit operator.
70
+ #
71
+ # Returns the Fixnum precedence of `operator`.
72
+ def precedence(operator)
73
+ if operator.fname?
74
+ PRECEDENCE['()'][:val]
75
+ else
76
+ PRECEDENCE[operator.value][:val]
77
+ end
78
+ end
79
+
80
+ # Insert the multiplication operator (`*`) where it is needed inside
81
+ # the @units member. This is because in Electr the `*` is implicit.
82
+ #
83
+ # Examples
84
+ #
85
+ # # @units before: 2 3
86
+ # # @units after: 2 * 3
87
+ #
88
+ # Returns nothing.
89
+ def insert_multiplication
90
+ temp = []
91
+ while unit = @units.shift
92
+ temp << unit
93
+ if @units.first
94
+ if (@units.first.number? && unit.number?) ||
95
+ (@units.first.open_parenthesis? && unit.number?) ||
96
+ (@units.first.fname? && unit.number?)
97
+ temp << LexicalUnit.operator('*')
98
+ end
99
+ end
100
+ end
101
+ @units = temp
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,21 @@
1
+ module Electr
2
+
3
+ # A Syntaxer transform a list of lexical units into an AST.
4
+ class Syntaxer
5
+
6
+ # lexical_units - Array of LexicalUnits
7
+ def initialize(lexical_units)
8
+ @units = lexical_units
9
+ @ast = AST.new "ast"
10
+ end
11
+
12
+ # Public: Compile lexical units into an AST.
13
+ #
14
+ # Returns the AST.
15
+ def run
16
+ RootRule.new(@units, @ast).apply!
17
+ @ast
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,79 @@
1
+ module Electr
2
+
3
+ class Tokenizer
4
+
5
+ ONE_CHAR_SYMBOLS = %w( + - / )
6
+
7
+ def initialize(string)
8
+ @index = 0
9
+ @token = ''
10
+ @look_ahead = ''
11
+ @codeline = string
12
+ forward_look_ahead
13
+ end
14
+
15
+ def has_more_token?
16
+ @index <= @codeline.size
17
+ end
18
+
19
+ def next_token
20
+ ret = produce_next_token
21
+ skip_white
22
+ @token = ''
23
+ ret
24
+ end
25
+
26
+ private
27
+
28
+ def forward_look_ahead
29
+ @look_ahead = @codeline[@index, 1]
30
+ @index += 1
31
+ end
32
+
33
+ def produce_next_token
34
+ if @look_ahead =~ /[0-9]/
35
+ get_number
36
+ elsif ONE_CHAR_SYMBOLS.include?(@look_ahead)
37
+ add_this_char
38
+ elsif @look_ahead == '('
39
+ add_this_char
40
+ elsif @look_ahead == ')'
41
+ add_this_char
42
+ else
43
+ get_word
44
+ end
45
+ end
46
+
47
+ def skip_white
48
+ forward_look_ahead while @look_ahead == ' '
49
+ end
50
+
51
+ def get_number
52
+ add_look_ahead while @look_ahead =~ /[0-9.]/
53
+ if @token[-1] != '.'
54
+ add_look_ahead while @look_ahead =~ /[kKRuFpΩμAmWV]/
55
+ end
56
+ @token
57
+ end
58
+
59
+ def add_look_ahead
60
+ @token << @look_ahead
61
+ forward_look_ahead
62
+ end
63
+
64
+ def get_word
65
+ add_look_ahead while @look_ahead =~ /\w/
66
+ @token
67
+ end
68
+
69
+ # Add current character to the current token and return it.
70
+ # Usefull shorthand for single character's tokens.
71
+ #
72
+ # Returns the String current token.
73
+ def add_this_char
74
+ add_look_ahead
75
+ @token
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,56 @@
1
+ module Electr
2
+
3
+ class Repl
4
+
5
+ BANNER = "\nElectr version #{Electr::VERSION}\n" +
6
+ "A tiny language for electronic formulas\n\n" +
7
+ "Hit Ctrl+C to quit Electr\n\n"
8
+
9
+ def initialize(options)
10
+ @options = options
11
+ puts BANNER
12
+ end
13
+
14
+ def run
15
+ if @options[:ast]
16
+ print_ast(read_ast) while true
17
+ else
18
+ _print(_eval(_read)) while true
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def _read
25
+ print("E> ")
26
+ line = STDIN.gets.chomp
27
+ compiler = Compiler.new
28
+ compiler.compile_to_pn(line)
29
+ end
30
+
31
+ def read_ast
32
+ print("E--ast> ")
33
+ line = STDIN.gets.chomp
34
+ compiler = Compiler.new
35
+ compiler.compile_to_ast(line)
36
+ end
37
+
38
+ def _eval(pns)
39
+ evaluator = Evaluator.new
40
+ evaluator.evaluate_pn(pns)
41
+ end
42
+
43
+ def _print(result)
44
+ if result == result.truncate
45
+ puts result.truncate
46
+ else
47
+ puts result.round(10)
48
+ end
49
+ end
50
+
51
+ def print_ast(ast)
52
+ ast.display
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ require "electr/parser/rules/base_rule"
2
+ require "electr/parser/rules/root_rule"
3
+ require "electr/parser/rules/expression_rule"
@@ -0,0 +1,3 @@
1
+ module Electr
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ include Electr
4
+
5
+ describe Compiler do
6
+
7
+ CODES_TO_COMPILE = [
8
+ "21 + 3",
9
+ "2",
10
+ "2 pi",
11
+ "2 11K",
12
+ "sqrt(49) + 1",
13
+ "sqrt(49)",
14
+ "1 + sqrt(49)",
15
+ "3 / (2 + 1)",
16
+ "sqrt(40 + 9)",
17
+ "3 (2 + 1)",
18
+ "2 sqrt(3)",
19
+ ]
20
+
21
+ specify "#compile_to_ast" do
22
+ CODES_TO_COMPILE.each do |code|
23
+ compiler = Compiler.new
24
+ result = compiler.compile_to_ast(code)
25
+ expect(result).to be_a AST
26
+ end
27
+ end
28
+
29
+ specify do
30
+ compiler = Compiler.new
31
+ result = compiler.compile_to_ast("2 pi 0.5uF sqrt(11K 22K)")
32
+ result.display
33
+ expect(result).to be_a AST
34
+ end
35
+
36
+ specify "#compile_to_pn" do
37
+ compiler = Compiler.new
38
+ result = compiler.compile_to_pn("2 pi")
39
+ expect(result).to be_a Array
40
+ end
41
+
42
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Electr do
4
+ it 'has a version number' do
5
+ expect(Electr::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ include Electr
4
+
5
+ describe Evaluator do
6
+
7
+ CODES_TO_EVAL = [
8
+ {code: "2", result: 2},
9
+ {code: "2 3", result: 6},
10
+ {code: "2 pi", result: 2 * Math::PI},
11
+ {code: "2 + 3", result: 5},
12
+ {code: "2 - 3", result: -1},
13
+ {code: "3 / 2", result: 1.5},
14
+ {code: "2 11k", result: 22_000},
15
+ {code: "sqrt(49)", result: 7},
16
+ {code: "3V / 25mA", result: 120},
17
+ {code: "110V / 2A", result: 55},
18
+ {code: "10mV 100R", result: 1},
19
+ {code: "10W 2", result: 20},
20
+ {code: "100mW 2", result: 0.2},
21
+ ]
22
+
23
+ specify "#evaluate_pn" do
24
+ CODES_TO_EVAL.each do |code|
25
+ compiler = Compiler.new
26
+ pns = compiler.compile_to_pn(code[:code])
27
+
28
+ evaluator = Evaluator.new
29
+ result = evaluator.evaluate_pn(pns)
30
+
31
+ expect(result).to eq code[:result]
32
+ end
33
+ end
34
+
35
+ specify do
36
+ compiler = Compiler.new
37
+ pns = compiler.compile_to_pn("1 - 1 + 4 - 1 - 1")
38
+
39
+ evaluator = Evaluator.new
40
+ result = evaluator.evaluate_pn(pns)
41
+
42
+ expect(result).to eq 2
43
+ end
44
+
45
+
46
+
47
+ end