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.
- 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
|
+
|