electr 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +196 -0
- data/Rakefile +7 -0
- data/bin/electr +27 -0
- data/electr.gemspec +25 -0
- data/lib/electr.rb +20 -0
- data/lib/electr/ast.rb +9 -0
- data/lib/electr/ast/ast.rb +62 -0
- data/lib/electr/ast/constant_ast.rb +14 -0
- data/lib/electr/ast/func_args_ast.rb +11 -0
- data/lib/electr/ast/func_ast.rb +12 -0
- data/lib/electr/ast/func_name_ast.rb +14 -0
- data/lib/electr/ast/numeric_ast.rb +13 -0
- data/lib/electr/ast/operator_ast.rb +13 -0
- data/lib/electr/ast/root_ast.rb +11 -0
- data/lib/electr/ast/value_ast.rb +12 -0
- data/lib/electr/compiler.rb +24 -0
- data/lib/electr/evaluator.rb +64 -0
- data/lib/electr/exceptions.rb +6 -0
- data/lib/electr/option.rb +70 -0
- data/lib/electr/parser.rb +5 -0
- data/lib/electr/parser/lexer.rb +50 -0
- data/lib/electr/parser/lexical_unit.rb +67 -0
- data/lib/electr/parser/rules/base_rule.rb +60 -0
- data/lib/electr/parser/rules/expression_rule.rb +79 -0
- data/lib/electr/parser/rules/root_rule.rb +21 -0
- data/lib/electr/parser/sya.rb +105 -0
- data/lib/electr/parser/syntaxer.rb +21 -0
- data/lib/electr/parser/tokenizer.rb +79 -0
- data/lib/electr/repl.rb +56 -0
- data/lib/electr/rules.rb +3 -0
- data/lib/electr/version.rb +3 -0
- data/spec/compiler_spec.rb +42 -0
- data/spec/electr_spec.rb +7 -0
- data/spec/evaluator_spec.rb +47 -0
- data/spec/lexer_spec.rb +29 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/sya_spec.rb +153 -0
- data/spec/tokenizer_spec.rb +102 -0
- metadata +138 -0
@@ -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,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,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
|
+
|