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,11 @@
1
+ module Electr
2
+
3
+ class FuncArgsAST < AST
4
+
5
+ def initialize
6
+ super("funcargs")
7
+ end
8
+
9
+ end
10
+ end
11
+
@@ -0,0 +1,12 @@
1
+ module Electr
2
+
3
+ class FuncAST < AST
4
+
5
+ def initialize
6
+ super("func")
7
+ end
8
+
9
+ end
10
+ end
11
+
12
+
@@ -0,0 +1,14 @@
1
+ module Electr
2
+
3
+ class FuncNameAST < AST
4
+
5
+ def initialize(value)
6
+ super("funcname")
7
+ @value = value
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+
14
+
@@ -0,0 +1,13 @@
1
+ module Electr
2
+
3
+ class NumericAST < AST
4
+
5
+ def initialize(value)
6
+ super("numeric")
7
+ @value = value
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,13 @@
1
+ module Electr
2
+
3
+ class OperatorAST < AST
4
+
5
+ def initialize(value)
6
+ super("operator")
7
+ @value = value
8
+ end
9
+
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,11 @@
1
+ module Electr
2
+
3
+ class RootAST < AST
4
+
5
+ def initialize
6
+ super("root")
7
+ end
8
+
9
+ end
10
+ end
11
+
@@ -0,0 +1,12 @@
1
+ module Electr
2
+
3
+ class ValueAST < AST
4
+
5
+ def initialize(value)
6
+ super("value")
7
+ @value = value
8
+ end
9
+
10
+ end
11
+ end
12
+
@@ -0,0 +1,24 @@
1
+ module Electr
2
+
3
+ class Compiler
4
+
5
+ def compile_to_ast(code)
6
+ units = []
7
+ tokenizer = Tokenizer.new(code)
8
+ lexer = Lexer.new
9
+ while tokenizer.has_more_token?
10
+ units << lexer.lexify(tokenizer.next_token)
11
+ end
12
+
13
+ syntaxer = Syntaxer.new(units.dup)
14
+ ast = syntaxer.run
15
+ end
16
+
17
+ # To Prefix Notation.
18
+ def compile_to_pn(code)
19
+ ast = compile_to_ast(code)
20
+ ast.to_pn
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,64 @@
1
+ module Electr
2
+
3
+ class Evaluator
4
+
5
+ def initialize
6
+ @stack = []
7
+ end
8
+
9
+ def evaluate_pn(list)
10
+ while item = list.pop
11
+ case item.name
12
+ when "numeric" then @stack.push(item.value.to_f)
13
+ when "constant" then @stack.push(constant(item.value))
14
+ when "value" then do_value(item.value)
15
+ when "operator" then operation(item.value)
16
+ when "funcname" then func(item.value)
17
+ end
18
+ end
19
+
20
+ @stack.pop
21
+ end
22
+
23
+ private
24
+
25
+ def constant(value)
26
+ case value
27
+ when "pi" then Math::PI
28
+ end
29
+ end
30
+
31
+ def func(name)
32
+ case name
33
+ when "sqrt"
34
+ a = @stack.pop
35
+ @stack.push(Math::sqrt(a))
36
+ end
37
+ end
38
+
39
+ def operation(operand)
40
+ a = @stack.pop
41
+ b = @stack.pop
42
+ @stack.push(a.send(operand, b))
43
+ end
44
+
45
+ def do_value(str)
46
+ # FIXME It's going to be better to separate the check for the unit
47
+ # (ohm, volt, ampere, etc) and for the prefix ([m]illi, [k]ilo,
48
+ # etc).
49
+ val = str.to_f
50
+ if %w( k K ).include?(str[-1]) || %w( kΩ ).include?(str[-2..-1])
51
+ val *= 1_000
52
+ elsif %w( mA mV mW ).include?(str[-2..-1])
53
+ val *= 0.001
54
+ elsif %w( u μ ).include?(str[-1]) || %w( μF uF ).include?(str[-2..-1])
55
+ val *= 0.000_001
56
+ elsif str[-1] == 'p' || str[-2..-1] == 'pF'
57
+ val *= 0.000_000_001
58
+ end
59
+ @stack.push(val)
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,6 @@
1
+ module Electr
2
+
3
+ class SyntaxError < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,70 @@
1
+ module Electr
2
+
3
+ # Process command line switches.
4
+ #
5
+ # The keys you are going to use:
6
+ #
7
+ # :ast - Boolean, if true the user want to print the Abstract
8
+ # Syntax Tree instead of the result.
9
+ # :expression - Don't run interactively, parse this String and quit.
10
+ #
11
+ # Examples
12
+ #
13
+ # opt = Option.new
14
+ # if opt[:expression]
15
+ # puts "Doing the job with #{opt[:expression]}"
16
+ # end
17
+ class Option
18
+
19
+ # Creates a new Option instance.
20
+ def initialize
21
+ @options = { ast: false }
22
+
23
+ optparse = OptionParser.new {|opts| parse(opts) }
24
+
25
+ begin
26
+ optparse.parse!
27
+ rescue OptionParser::InvalidOption => exception
28
+ puts exception.to_s
29
+ exit 1
30
+ end
31
+
32
+ print_version if @options[:version]
33
+ end
34
+
35
+ # Get an option.
36
+ #
37
+ # key - The Symbol name of the option to get.
38
+ #
39
+ # Returns Any value corresponding of the key, or nil if the key
40
+ # doesn't exists.
41
+ def [](key)
42
+ @options[key]
43
+ end
44
+
45
+ def parse(opts)
46
+ opts.on(nil, '--ast', 'Display AST and quit') do
47
+ @options[:ast] = true
48
+ end
49
+ opts.on('-e', '--expression EXP', 'Compute EXP and quit') do |arg|
50
+ @options[:expression] = arg
51
+ end
52
+ opts.on('-v', '--version', 'Print version number and exit') do
53
+ @options[:version] = true
54
+ end
55
+ opts.on('-h', '--help', 'Display this screen') do
56
+ puts opts
57
+ exit
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def print_version
64
+ puts "Electr version #{VERSION}"
65
+ exit
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,5 @@
1
+ require "electr/parser/tokenizer"
2
+ require "electr/parser/lexer"
3
+ require "electr/parser/lexical_unit"
4
+ require "electr/parser/syntaxer"
5
+ require "electr/parser/sya"
@@ -0,0 +1,50 @@
1
+ module Electr
2
+
3
+ class Lexer
4
+
5
+ # Public: Build a LexicalUnit from a given token.
6
+ #
7
+ # token - A String token to transform in LexicalUnit.
8
+ #
9
+ # Returns a LexicalUnit.
10
+ def lexify(token)
11
+ if numeric?(token)
12
+ LexicalUnit.numeric(token)
13
+ elsif operator?(token)
14
+ LexicalUnit.operator(token)
15
+ elsif constant?(token)
16
+ LexicalUnit.constant(token)
17
+ elsif value?(token)
18
+ LexicalUnit.value(token)
19
+ elsif token == '('
20
+ LexicalUnit.open_parenthesis()
21
+ elsif token == ')'
22
+ LexicalUnit.closed_parenthesis()
23
+ elsif SYMBOL_TABLE[token] == 'f'
24
+ LexicalUnit.fname(token)
25
+ else
26
+ LexicalUnit.name(token)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def numeric?(token)
33
+ token =~ /[0-9.]\Z/
34
+ end
35
+
36
+ def operator?(token)
37
+ %w( + - / ).include?(token)
38
+ end
39
+
40
+ def constant?(token)
41
+ %w( pi ).include?(token)
42
+ end
43
+
44
+ def value?(token)
45
+ # The unit part is redondant with Tokenizer.
46
+ token =~ /[0-9.][kKRuFpΩμAmWV]{1,}\Z/
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ module Electr
2
+
3
+ class LexicalUnit
4
+
5
+ private_class_method :new
6
+
7
+ attr_reader :type, :value
8
+
9
+ # type - Symbol
10
+ # value - String
11
+ def initialize(type, value)
12
+ @type = type
13
+ @value = value
14
+ end
15
+
16
+ def self.numeric(value) ; new(:numeric, value) ; end
17
+ def self.operator(value) ; new(:operator, value) ; end
18
+ def self.constant(value) ; new(:constant, value) ; end
19
+ def self.value(value) ; new(:value, value) ; end
20
+ def self.name(value) ; new(:name, value) ; end
21
+ def self.fname(value) ; new(:fname, value) ; end
22
+ def self.fcall(value) ; new(:fcall, value) ; end
23
+ def self.open_parenthesis ; new(:open_parenthesis, "(") ; end
24
+ def self.closed_parenthesis ; new(:closed_parenthesis, ")") ; end
25
+
26
+ def ==(other)
27
+ @type == other.type && @value == other.value
28
+ end
29
+
30
+ def numeric?
31
+ @type == :numeric
32
+ end
33
+
34
+ def operator?
35
+ @type == :operator
36
+ end
37
+
38
+ def constant?
39
+ @type == :constant
40
+ end
41
+
42
+ def value?
43
+ @type == :value
44
+ end
45
+
46
+ def fname?
47
+ @type == :fname
48
+ end
49
+
50
+ def fcall?
51
+ @type == :fcall
52
+ end
53
+
54
+ def number?
55
+ numeric? || constant? || value?
56
+ end
57
+
58
+ def closed_parenthesis?
59
+ @type == :closed_parenthesis
60
+ end
61
+
62
+ def open_parenthesis?
63
+ @type == :open_parenthesis
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ module Electr
2
+
3
+ class BaseRule
4
+
5
+ # Initialize a new rule. The units and ast_node objects
6
+ # given in arguments will be modified by #apply!.
7
+ #
8
+ # units - Array of LexicalUnits.
9
+ # ast_node - An AST object, for adding children, etc.
10
+ def initialize(units, ast_node)
11
+ @units = units
12
+ @ast_node = ast_node
13
+ @series = []
14
+ end
15
+
16
+ # Public: Apply the rule to the list of lexical units, creating
17
+ # and/or modifying the ast node. #apply! modify the units and
18
+ # ast_node objects given to #initialize.
19
+ def apply!
20
+ raise NotImplementedError
21
+ end
22
+
23
+ private
24
+
25
+ # Accepts the next lexical unit, given type match the unit type.
26
+ # If value is given, it has to match the unit value.
27
+ #
28
+ # type - Expected Symbol type of the next unit.
29
+ # value - Expected String value of the next unit. Default is the
30
+ # empty string, meaning the value doesn't matter.
31
+ #
32
+ # Returns nothing.
33
+ # Raises SyntaxError if type or value doesn't match.
34
+ def accept(type, value = '')
35
+ unit = @units.slice!(0)
36
+ @series << unit
37
+ unless unit.type == type
38
+ raise(SyntaxError, "Expected type : #{type}\n" +
39
+ "Expected value: #{value}\n" +
40
+ "Got type : #{unit.type}\n" +
41
+ "Got value : #{unit.value}")
42
+ end
43
+ if value != ''
44
+ raise SyntaxError unless unit.value == value
45
+ end
46
+ end
47
+
48
+ # A shortcut method to #accept. Usefull when we have several calls
49
+ # to #accept. Calls #accept for each of the symbol in args.
50
+ #
51
+ # args - A series of Symbol.
52
+ #
53
+ # Returns nothing.
54
+ def accept_all(*args)
55
+ args.each {|arg| accept(arg) }
56
+ end
57
+
58
+ end
59
+ end
60
+