erle 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +79 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +6 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/erle.gemspec +34 -0
- data/lib/erle.rb +28 -0
- data/lib/erle/elements/atom.rb +44 -0
- data/lib/erle/elements/binary.rb +31 -0
- data/lib/erle/elements/enum.rb +69 -0
- data/lib/erle/elements/list.rb +9 -0
- data/lib/erle/elements/map.rb +9 -0
- data/lib/erle/elements/numbers.rb +26 -0
- data/lib/erle/elements/pid.rb +22 -0
- data/lib/erle/elements/ref.rb +37 -0
- data/lib/erle/elements/string.rb +33 -0
- data/lib/erle/elements/term.rb +71 -0
- data/lib/erle/elements/tuple.rb +37 -0
- data/lib/erle/errors.rb +12 -0
- data/lib/erle/parser.rb +122 -0
- data/lib/erle/registry.rb +71 -0
- data/lib/erle/version.rb +3 -0
- data/spec/erle/parser/atom_spec.rb +33 -0
- data/spec/erle/parser/binary_spec.rb +19 -0
- data/spec/erle/parser/enum_spec.rb +20 -0
- data/spec/erle/parser/numbers_spec.rb +21 -0
- data/spec/erle/parser/parser_spec.rb +135 -0
- data/spec/erle/parser/pid_spec.rb +18 -0
- data/spec/erle/parser/ref_spec.rb +20 -0
- data/spec/erle/parser/term_spec.rb +20 -0
- data/spec/erle/parser/tuple_spec.rb +27 -0
- data/spec/erle/parser_spec.rb +7 -0
- data/spec/spec_helper.rb +22 -0
- metadata +153 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
class Number < Term
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
class Float < Number
|
8
|
+
pattern %r{[-0-9]\.[0-9]+}
|
9
|
+
|
10
|
+
def to_ruby
|
11
|
+
@output ||= @input.to_f
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class Integer < Number
|
17
|
+
|
18
|
+
pattern %r{(-?0|-?[1-9]\d*)}
|
19
|
+
|
20
|
+
def to_ruby
|
21
|
+
@output ||= @input.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
# http://erlang.org/doc/reference_manual/data_types.html#id68255
|
4
|
+
class Pid < Term
|
5
|
+
|
6
|
+
class Data < ::String
|
7
|
+
|
8
|
+
def to_erl
|
9
|
+
"<#{self}>"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
enclosure /</, />/
|
14
|
+
pattern %r{[-0-9](\.[0-9])+}
|
15
|
+
# @delimeter = '.'
|
16
|
+
|
17
|
+
def to_ruby
|
18
|
+
@output ||= Data.new(@input)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
class Ref < Term
|
4
|
+
enclosure /\#Ref</, />/
|
5
|
+
|
6
|
+
@delimeter = "."
|
7
|
+
|
8
|
+
@pattern = %r{(?:(\d+)\.?)+}
|
9
|
+
|
10
|
+
def initialize(input)
|
11
|
+
@input = input
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO: Leverage Enum, and refactor (Enum) to handle conflicting delimiters
|
15
|
+
# e.g. a "." delimeter is preempted by the "float" pattern.
|
16
|
+
def to_ruby
|
17
|
+
@output ||= @input.split(".")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(parser)
|
21
|
+
|
22
|
+
result = parser.scan(@pattern)
|
23
|
+
|
24
|
+
if close && !parser.scan(close)
|
25
|
+
raise parser.raise_unexpected_token("Expected term closure \" #{close.source} \"")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Make sure we kill any trailing close
|
29
|
+
# TODO: Consider raising if no match?
|
30
|
+
# TODO: Consider doing only if we started with an opening...
|
31
|
+
# parser.scan(ERLE::Registry.closings_regex)
|
32
|
+
new(result)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
class String < Term
|
4
|
+
enclosure /\"/
|
5
|
+
# pattern %r{((?:[^\x0-\x1f'\\] |
|
6
|
+
# # escaped special characters:
|
7
|
+
# \\["/bfnrt] |
|
8
|
+
# \\u[0-9a-fA-F]{4} |
|
9
|
+
# # match all but escaped special characters:
|
10
|
+
# \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
|
11
|
+
# }nx
|
12
|
+
|
13
|
+
PATTERN = %r{[^\"]*}nx
|
14
|
+
|
15
|
+
def to_ruby
|
16
|
+
# self.str.gsub(/"/,"")
|
17
|
+
@output ||= @input
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(parser)
|
21
|
+
opener = parser.matched == "\""
|
22
|
+
|
23
|
+
parser.scan(PATTERN)
|
24
|
+
result = parser.matched
|
25
|
+
|
26
|
+
parser.scan(close) if opener
|
27
|
+
|
28
|
+
new(result)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
class Term
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :open, :close, :patterns
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :input
|
10
|
+
attr_accessor :output
|
11
|
+
|
12
|
+
|
13
|
+
def self.patterns
|
14
|
+
@patterns ||= Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.pattern(p)
|
18
|
+
patterns.add(p)
|
19
|
+
ERLE::Registry.pattern(self, p)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.enclosure(one, two=one)
|
23
|
+
@open = one.freeze
|
24
|
+
@close = two.freeze
|
25
|
+
ERLE::Registry.enclosure(self, one, two)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(input)
|
29
|
+
@input = input
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_ruby
|
33
|
+
@input
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.to_ruby(value)
|
37
|
+
case value
|
38
|
+
when Term
|
39
|
+
value.to_ruby
|
40
|
+
else
|
41
|
+
value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.until_any_closing
|
46
|
+
@until_any_closing ||= Regexp.new("[^#{ERLE::Registry.closings_regex.source}]*")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse(parser)
|
50
|
+
|
51
|
+
patterns.find do |pattern|
|
52
|
+
parser.scan(pattern)
|
53
|
+
end
|
54
|
+
|
55
|
+
result = parser.matched
|
56
|
+
|
57
|
+
if close && !parser.scan(close)
|
58
|
+
raise parser.raise_unexpected_token("Expected term closure \" #{close.source} \"")
|
59
|
+
end
|
60
|
+
|
61
|
+
# # Make sure we kill any trailing close
|
62
|
+
# # TODO: Consider raising if no match?
|
63
|
+
# # TODO: Consider doing only if we started with an opening...
|
64
|
+
# parser.scan(ERLE::Registry.closings_regex)
|
65
|
+
# # binding.pry
|
66
|
+
new(result)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
class Tuple < Enum
|
4
|
+
enclosure /\{/, /\}/
|
5
|
+
@delimeter = ','
|
6
|
+
|
7
|
+
attr_accessor :key
|
8
|
+
|
9
|
+
def initialize(elements)
|
10
|
+
super
|
11
|
+
@key = Term.to_ruby(@terms.shift)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_ruby
|
15
|
+
@output ||= { @key => one_or_all(terms_to_ruby) }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module ERLE
|
23
|
+
|
24
|
+
class Luple < Enum
|
25
|
+
# register "[{", "}]"
|
26
|
+
|
27
|
+
def to_ruby
|
28
|
+
# @output = Enum.split("},{")
|
29
|
+
# arr = @output.map {|el|
|
30
|
+
# el.is_a?(Term) ? el.to_ruby : el
|
31
|
+
# }
|
32
|
+
# key = arr.delete_at(0)
|
33
|
+
# hash = {key => ( arr.length>1 ? arr: arr[0])}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/erle/errors.rb
ADDED
data/lib/erle/parser.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
require 'erle/registry'
|
3
|
+
|
4
|
+
module ERLE
|
5
|
+
|
6
|
+
class Parser < StringScanner
|
7
|
+
|
8
|
+
UNPARSED = Object.new.freeze
|
9
|
+
IGNORE = %r(
|
10
|
+
(?:
|
11
|
+
//[^\n\r]*[\n\r]| # line comments
|
12
|
+
/\* # c-style comments
|
13
|
+
(?:
|
14
|
+
[^*/]| # normal chars
|
15
|
+
/[^*]| # slashes that do not start a nested comment
|
16
|
+
\*[^/]| # asterisks that do not end this comment
|
17
|
+
/(?=\*/) # single slash before this comment's end
|
18
|
+
)*
|
19
|
+
\*/ # the End of this comment
|
20
|
+
|[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
|
21
|
+
)+
|
22
|
+
)mx
|
23
|
+
|
24
|
+
INTEGER = /(-?0|-?[1-9]\d*)/
|
25
|
+
FLOAT = /(-?
|
26
|
+
(?:0|[1-9]\d*)
|
27
|
+
(?:
|
28
|
+
\.\d+(?i:e[+-]?\d+) |
|
29
|
+
\.\d+ |
|
30
|
+
(?i:e[+-]?\d+)
|
31
|
+
)
|
32
|
+
)/x
|
33
|
+
TRUE = /true/
|
34
|
+
FALSE = /false/
|
35
|
+
|
36
|
+
def initialize(source, opts = {})
|
37
|
+
opts ||= {}
|
38
|
+
# source = convert_encoding source
|
39
|
+
super source
|
40
|
+
end
|
41
|
+
|
42
|
+
alias source string
|
43
|
+
|
44
|
+
def parse
|
45
|
+
reset
|
46
|
+
obj = nil
|
47
|
+
|
48
|
+
until_done do
|
49
|
+
if eos?
|
50
|
+
raise_parsing_error
|
51
|
+
else
|
52
|
+
obj = parse_value
|
53
|
+
UNPARSED.equal?(obj) and raise_parsing_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
eos? or raise_parsing_error
|
58
|
+
obj
|
59
|
+
end
|
60
|
+
|
61
|
+
def skip_ignore
|
62
|
+
skip(IGNORE)
|
63
|
+
end
|
64
|
+
|
65
|
+
def until_done
|
66
|
+
result = nil
|
67
|
+
while !eos?
|
68
|
+
skip_ignore
|
69
|
+
result = yield if block_given?
|
70
|
+
skip_ignore
|
71
|
+
end
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
def peekaboo(peek = 30, back = 30)
|
76
|
+
string[pos-back, peek+back]
|
77
|
+
end
|
78
|
+
|
79
|
+
def raise_parsing_error(message = "source is not valid Erlang!")
|
80
|
+
# warn "Parsing =>\n#{string}"
|
81
|
+
raise ParserError, "Parsing =>\n#{string}\n#{message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def whereami?(peek = 30, back = 30)
|
85
|
+
back = [back, string.length - pos].sort[0]
|
86
|
+
"#{peekaboo(peek, back)}'\n#{"─" * (back)}┘"
|
87
|
+
end
|
88
|
+
|
89
|
+
def raise_unexpected_token(followup=nil)
|
90
|
+
message = "Unexpected token at:\n#{whereami?}"
|
91
|
+
message += "\n#{followup}" if followup
|
92
|
+
raise ParserError, message
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_value
|
96
|
+
# TODO: handle symbols
|
97
|
+
skip_ignore
|
98
|
+
|
99
|
+
case
|
100
|
+
when scan(TRUE)
|
101
|
+
true
|
102
|
+
when scan(FALSE)
|
103
|
+
false
|
104
|
+
when !eos? && scan(ERLE::Registry.openings_regex) # TODO: Take out !eos?
|
105
|
+
regex, term_class = ERLE::Registry.open_find(matched)
|
106
|
+
term_str = term_class.parse(self)
|
107
|
+
else
|
108
|
+
term_class, regex = ERLE::Registry.pattern_find do |p|
|
109
|
+
match?(p)
|
110
|
+
end
|
111
|
+
|
112
|
+
if term_class
|
113
|
+
term_class.parse(self)
|
114
|
+
else
|
115
|
+
UNPARSED
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ERLE
|
2
|
+
|
3
|
+
module Registry
|
4
|
+
class << self
|
5
|
+
attr_accessor :_enclosures
|
6
|
+
attr_accessor :_patterns
|
7
|
+
end
|
8
|
+
|
9
|
+
@_enclosures = {}
|
10
|
+
@_patterns = {}
|
11
|
+
|
12
|
+
def self.enclosure(klass, one, two)
|
13
|
+
@_enclosures[one] = klass
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.pattern(klass, pat)
|
17
|
+
(@_patterns[klass] ||= Set.new).add(pat)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.pattern_find
|
21
|
+
return unless block_given?
|
22
|
+
@_patterns.each do |klass, patterns|
|
23
|
+
pattern = patterns.find do |pattern|
|
24
|
+
yield(pattern)
|
25
|
+
end
|
26
|
+
return [klass, pattern] if pattern
|
27
|
+
end
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.open_find(str)
|
32
|
+
sorted_encolsures.find do |k, v|
|
33
|
+
str =~ k
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.sorted_encolsures
|
38
|
+
@sorted_encolsures ||= @_enclosures.sort_by { |pattern, klass| pattern.source.length }.reverse.to_h
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.openings
|
42
|
+
@openings ||= sorted_encolsures.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.openings_source
|
46
|
+
@openings_source ||= openings.map(&:source)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.openings_regex
|
50
|
+
@openings_regex ||= Regexp.new("(#{openings_source.join('|')})")
|
51
|
+
end
|
52
|
+
|
53
|
+
# def self.close_find(str)
|
54
|
+
# @_enclosures.values.find do |klass|
|
55
|
+
# str =~ klass.close
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
|
59
|
+
# def self.closings
|
60
|
+
# @closings ||= @_enclosures.values.collect(&:close).sort { |a, b| b.source.length <=> a.source.length }
|
61
|
+
# end
|
62
|
+
|
63
|
+
# def self.closings_source
|
64
|
+
# @closings_source ||= closings.map(&:source)
|
65
|
+
# end
|
66
|
+
|
67
|
+
# def self.closings_regex
|
68
|
+
# @closings_regex ||= Regexp.new("(#{closings_source.join('|')})")
|
69
|
+
# end
|
70
|
+
end
|
71
|
+
end
|