electr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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