poetics 0.0.1
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.
- data/.gitignore +5 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README +87 -0
- data/Rakefile +21 -0
- data/bin/poetics +140 -0
- data/lib/poetics.rb +4 -0
- data/lib/poetics/library.rb +1 -0
- data/lib/poetics/library/code_loader.rb +13 -0
- data/lib/poetics/parser.rb +21 -0
- data/lib/poetics/parser/parser.rb +875 -0
- data/lib/poetics/parser/poetics.kpeg +42 -0
- data/lib/poetics/syntax.rb +3 -0
- data/lib/poetics/syntax/ast.rb +31 -0
- data/lib/poetics/syntax/literal.rb +65 -0
- data/lib/poetics/syntax/node.rb +15 -0
- data/lib/poetics/version.rb +5 -0
- data/poetics.gemspec +23 -0
- data/spec/custom.rb +4 -0
- data/spec/custom/matchers/parse_as.rb +21 -0
- data/spec/custom/runner/relates.rb +97 -0
- data/spec/custom/utils/options.rb +21 -0
- data/spec/custom/utils/script.rb +26 -0
- data/spec/default.mspec +6 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/syntax/literal_spec.rb +60 -0
- metadata +98 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Brian Ford. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
A native implementation of CoffeeScript [1] that runs on the Rubinius VM [2].
|
2
|
+
|
3
|
+
Q. Whence comes the name Poetics?
|
4
|
+
A. Poetics is a partial anagram of the word CoffeeScript. It is also a nod to
|
5
|
+
Jeremy Ashkenas, the author of CoffeeScript, and his interest in code as
|
6
|
+
literature.
|
7
|
+
|
8
|
+
Q. Why a native implementation?
|
9
|
+
A. CoffeeScript is an interesting language in its own right. Rather than
|
10
|
+
merely being a syntax layer on top of Javascript, and bound to expressing
|
11
|
+
its semantics in those of Javascript, it deserves its own implementation.
|
12
|
+
Many of the reasons to use CoffeeScript in Node.js would also apply to
|
13
|
+
using this native implementation.
|
14
|
+
|
15
|
+
|
16
|
+
Installation
|
17
|
+
|
18
|
+
First, install Rubinius:
|
19
|
+
|
20
|
+
1. Using RVM (http://rvm.beginrescueend.com).
|
21
|
+
|
22
|
+
rvm install rbx
|
23
|
+
|
24
|
+
2. Or directly (http://rubini.us/doc/en/what-is-rubinius/).
|
25
|
+
|
26
|
+
Second, install Poetics:
|
27
|
+
|
28
|
+
rbx -S gem install poetics
|
29
|
+
|
30
|
+
|
31
|
+
Running Poetics
|
32
|
+
|
33
|
+
Poetics provides a REPL for exploratory programming or runs CoffeeScript
|
34
|
+
scripts.
|
35
|
+
|
36
|
+
rbx -S poetics -h
|
37
|
+
|
38
|
+
The REPL presently just parses code and returns an S-Expression
|
39
|
+
representation:
|
40
|
+
|
41
|
+
$ rbx -S poetics
|
42
|
+
> 42
|
43
|
+
[:number, 42.0, 1, 1]
|
44
|
+
> "hello, y'all"
|
45
|
+
[:string, "hello, y'all", 1, 1]
|
46
|
+
>
|
47
|
+
|
48
|
+
|
49
|
+
Getting Help
|
50
|
+
|
51
|
+
Poetics is a work in progress. If you encounter trouble, please file an issue
|
52
|
+
at https://github.com/brixen/poetics/issues
|
53
|
+
|
54
|
+
|
55
|
+
Contributing
|
56
|
+
|
57
|
+
If you find Poetics interesting and would like to help out, fork the project
|
58
|
+
on Github and submit a pull request.
|
59
|
+
|
60
|
+
|
61
|
+
People
|
62
|
+
|
63
|
+
Poetics was created by Brian Ford (brixen) to force him to really learn
|
64
|
+
Javascript and CoffeeScript.
|
65
|
+
|
66
|
+
<add your name here>
|
67
|
+
|
68
|
+
|
69
|
+
License
|
70
|
+
|
71
|
+
Poetics is MIT licensed. See the LICENSE file.
|
72
|
+
|
73
|
+
|
74
|
+
Credits
|
75
|
+
|
76
|
+
Jeremy has created an very interesting language in CoffeeScript. This
|
77
|
+
implementation steals bits and pieces from other projects:
|
78
|
+
|
79
|
+
Rubinius (https://github.com/evanphx/rubinius/)
|
80
|
+
KPeg (https://github.com/evanphx/kpeg/)
|
81
|
+
Atomy (https://github.com/vito/atomy/)
|
82
|
+
Poison (https://github.com/brixen/poison/)
|
83
|
+
Talon (https://github.com/evanphx/talon/)
|
84
|
+
|
85
|
+
|
86
|
+
[1] CoffeeScript (http://jashkenas.github.com/coffee-script/)
|
87
|
+
[2] Rubinius (http://rubini.us)
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
base = File.expand_path '../lib/poetics/parser', __FILE__
|
7
|
+
|
8
|
+
grammar = "#{base}/poetics.kpeg"
|
9
|
+
parser = "#{base}/parser.rb"
|
10
|
+
|
11
|
+
file parser => grammar do
|
12
|
+
sh "rbx -S kpeg -f -s #{base}/poetics.kpeg -o #{base}/parser.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Convert the grammar description to a parser"
|
16
|
+
task :parser => parser
|
17
|
+
|
18
|
+
desc "Run the specs (default)"
|
19
|
+
task :spec => :parser do
|
20
|
+
sh "mspec spec"
|
21
|
+
end
|
data/bin/poetics
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env rbx
|
2
|
+
#
|
3
|
+
# vim: filetype=ruby
|
4
|
+
|
5
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
6
|
+
|
7
|
+
require 'readline'
|
8
|
+
require 'poetics'
|
9
|
+
|
10
|
+
class Poetics::Script
|
11
|
+
HISTORY = File.expand_path "~/.poetics_history"
|
12
|
+
|
13
|
+
attr_accessor :prompt
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@evals = []
|
17
|
+
@script = nil
|
18
|
+
@history = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def options(argv=ARGV)
|
22
|
+
options = Rubinius::Options.new "Usage: poetics [options] [script]", 20
|
23
|
+
|
24
|
+
options.on "-", "Read and evaluate code from STDIN" do
|
25
|
+
@evals << STDIN.read
|
26
|
+
end
|
27
|
+
|
28
|
+
options.on "-c", "FILE", "Check the syntax of FILE" do |f|
|
29
|
+
begin
|
30
|
+
begin
|
31
|
+
Poetics::Parser.parse_file f
|
32
|
+
rescue => e
|
33
|
+
e.render
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "Syntax OK"
|
38
|
+
exit 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
options.on "-e", "CODE", "Execute CODE" do |e|
|
43
|
+
@evals << e
|
44
|
+
end
|
45
|
+
|
46
|
+
options.on "-v", "--version", "Display the version" do
|
47
|
+
puts Poetics::COMMAND_VERSION
|
48
|
+
end
|
49
|
+
|
50
|
+
options.on "-h", "--help", "Display this help" do
|
51
|
+
puts options
|
52
|
+
exit 0
|
53
|
+
end
|
54
|
+
|
55
|
+
options.doc ""
|
56
|
+
|
57
|
+
rest = options.parse(argv)
|
58
|
+
@script ||= rest.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def evals
|
62
|
+
return if @evals.empty?
|
63
|
+
|
64
|
+
Poetics::CodeLoader.evaluate @evals.join("\n")
|
65
|
+
end
|
66
|
+
|
67
|
+
def script
|
68
|
+
return unless @script
|
69
|
+
|
70
|
+
if File.exists? @script
|
71
|
+
Poetics::CodeLoader.execute_file @script
|
72
|
+
else
|
73
|
+
STDERR.puts "Unable to find '#{@script}' to run"
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_history
|
79
|
+
return unless File.exists? HISTORY
|
80
|
+
|
81
|
+
File.open HISTORY, "r" do |f|
|
82
|
+
f.each { |l| Readline::HISTORY << l }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_history
|
87
|
+
File.open HISTORY, "w" do |f|
|
88
|
+
@history.last(100).map { |s| f.puts s }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def record_history(str)
|
93
|
+
@history << str
|
94
|
+
end
|
95
|
+
|
96
|
+
def start_prompt
|
97
|
+
@prompt = "> "
|
98
|
+
end
|
99
|
+
|
100
|
+
def continue_prompt
|
101
|
+
@prompt = ". "
|
102
|
+
end
|
103
|
+
|
104
|
+
def repl
|
105
|
+
return if @script
|
106
|
+
load_history
|
107
|
+
start_prompt
|
108
|
+
|
109
|
+
begin
|
110
|
+
snippet = ""
|
111
|
+
while str = Readline.readline(prompt)
|
112
|
+
break if str.nil? and snippet.empty?
|
113
|
+
next if str.empty?
|
114
|
+
|
115
|
+
snippet << str
|
116
|
+
begin
|
117
|
+
value = Poetics::CodeLoader.evaluate snippet
|
118
|
+
p value
|
119
|
+
|
120
|
+
record_history snippet
|
121
|
+
snippet = ""
|
122
|
+
start_prompt
|
123
|
+
rescue => e
|
124
|
+
STDERR.puts e
|
125
|
+
end
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
save_history
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def main
|
133
|
+
options
|
134
|
+
evals
|
135
|
+
script
|
136
|
+
repl
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
Poetics::Script.new.main
|
data/lib/poetics.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'poetics/library/code_loader'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Poetics
|
2
|
+
class CodeLoader
|
3
|
+
def self.evaluate(string)
|
4
|
+
# We're just parsing for now
|
5
|
+
Poetics::Parser.parse_to_sexp string
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.execute_file(name)
|
9
|
+
value = Poetics::Parser.parse_to_sexp IO.read(name)
|
10
|
+
p value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'poetics/parser/parser'
|
2
|
+
|
3
|
+
class Poetics::Parser
|
4
|
+
include Poetics::Syntax
|
5
|
+
|
6
|
+
def self.parse_to_sexp(string)
|
7
|
+
parser = new string
|
8
|
+
unless parser.parse
|
9
|
+
parser.raise_error
|
10
|
+
end
|
11
|
+
|
12
|
+
parser.result.to_sexp
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :line, :column
|
16
|
+
|
17
|
+
def position(line, column)
|
18
|
+
@line = line
|
19
|
+
@column = column
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,875 @@
|
|
1
|
+
class Poetics::Parser
|
2
|
+
# STANDALONE START
|
3
|
+
def setup_parser(str, debug=false)
|
4
|
+
@string = str
|
5
|
+
@pos = 0
|
6
|
+
@memoizations = Hash.new { |h,k| h[k] = {} }
|
7
|
+
@result = nil
|
8
|
+
@failed_rule = nil
|
9
|
+
@failing_rule_offset = -1
|
10
|
+
|
11
|
+
setup_foreign_grammar
|
12
|
+
end
|
13
|
+
|
14
|
+
# This is distinct from setup_parser so that a standalone parser
|
15
|
+
# can redefine #initialize and still have access to the proper
|
16
|
+
# parser setup code.
|
17
|
+
#
|
18
|
+
def initialize(str, debug=false)
|
19
|
+
setup_parser(str, debug)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :string
|
23
|
+
attr_reader :result, :failing_rule_offset
|
24
|
+
attr_accessor :pos
|
25
|
+
|
26
|
+
# STANDALONE START
|
27
|
+
def current_column(target=pos)
|
28
|
+
if c = string.rindex("\n", target-1)
|
29
|
+
return target - c - 1
|
30
|
+
end
|
31
|
+
|
32
|
+
target + 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_line(target=pos)
|
36
|
+
cur_offset = 0
|
37
|
+
cur_line = 0
|
38
|
+
|
39
|
+
string.each_line do |line|
|
40
|
+
cur_line += 1
|
41
|
+
cur_offset += line.size
|
42
|
+
return cur_line if cur_offset >= target
|
43
|
+
end
|
44
|
+
|
45
|
+
-1
|
46
|
+
end
|
47
|
+
|
48
|
+
def lines
|
49
|
+
lines = []
|
50
|
+
string.each_line { |l| lines << l }
|
51
|
+
lines
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
|
56
|
+
def get_text(start)
|
57
|
+
@string[start..@pos-1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def show_pos
|
61
|
+
width = 10
|
62
|
+
if @pos < width
|
63
|
+
"#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")"
|
64
|
+
else
|
65
|
+
"#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def failure_info
|
70
|
+
l = current_line @failing_rule_offset
|
71
|
+
c = current_column @failing_rule_offset
|
72
|
+
|
73
|
+
if @failed_rule.kind_of? Symbol
|
74
|
+
info = self.class::Rules[@failed_rule]
|
75
|
+
"line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'"
|
76
|
+
else
|
77
|
+
"line #{l}, column #{c}: failed rule '#{@failed_rule}'"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def failure_caret
|
82
|
+
l = current_line @failing_rule_offset
|
83
|
+
c = current_column @failing_rule_offset
|
84
|
+
|
85
|
+
line = lines[l-1]
|
86
|
+
"#{line}\n#{' ' * (c - 1)}^"
|
87
|
+
end
|
88
|
+
|
89
|
+
def failure_character
|
90
|
+
l = current_line @failing_rule_offset
|
91
|
+
c = current_column @failing_rule_offset
|
92
|
+
lines[l-1][c-1, 1]
|
93
|
+
end
|
94
|
+
|
95
|
+
def failure_oneline
|
96
|
+
l = current_line @failing_rule_offset
|
97
|
+
c = current_column @failing_rule_offset
|
98
|
+
|
99
|
+
char = lines[l-1][c-1, 1]
|
100
|
+
|
101
|
+
if @failed_rule.kind_of? Symbol
|
102
|
+
info = self.class::Rules[@failed_rule]
|
103
|
+
"@#{l}:#{c} failed rule '#{info.name}', got '#{char}'"
|
104
|
+
else
|
105
|
+
"@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class ParseError < RuntimeError
|
110
|
+
end
|
111
|
+
|
112
|
+
def raise_error
|
113
|
+
raise ParseError, failure_oneline
|
114
|
+
end
|
115
|
+
|
116
|
+
def show_error(io=STDOUT)
|
117
|
+
error_pos = @failing_rule_offset
|
118
|
+
line_no = current_line(error_pos)
|
119
|
+
col_no = current_column(error_pos)
|
120
|
+
|
121
|
+
io.puts "On line #{line_no}, column #{col_no}:"
|
122
|
+
|
123
|
+
if @failed_rule.kind_of? Symbol
|
124
|
+
info = self.class::Rules[@failed_rule]
|
125
|
+
io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')"
|
126
|
+
else
|
127
|
+
io.puts "Failed to match rule '#{@failed_rule}'"
|
128
|
+
end
|
129
|
+
|
130
|
+
io.puts "Got: #{string[error_pos,1].inspect}"
|
131
|
+
line = lines[line_no-1]
|
132
|
+
io.puts "=> #{line}"
|
133
|
+
io.print(" " * (col_no + 3))
|
134
|
+
io.puts "^"
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_failed_rule(name)
|
138
|
+
if @pos > @failing_rule_offset
|
139
|
+
@failed_rule = name
|
140
|
+
@failing_rule_offset = @pos
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :failed_rule
|
145
|
+
|
146
|
+
def match_string(str)
|
147
|
+
len = str.size
|
148
|
+
if @string[pos,len] == str
|
149
|
+
@pos += len
|
150
|
+
return str
|
151
|
+
end
|
152
|
+
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def scan(reg)
|
157
|
+
if m = reg.match(@string[@pos..-1])
|
158
|
+
width = m.end(0)
|
159
|
+
@pos += width
|
160
|
+
return true
|
161
|
+
end
|
162
|
+
|
163
|
+
return nil
|
164
|
+
end
|
165
|
+
|
166
|
+
if "".respond_to? :getbyte
|
167
|
+
def get_byte
|
168
|
+
if @pos >= @string.size
|
169
|
+
return nil
|
170
|
+
end
|
171
|
+
|
172
|
+
s = @string.getbyte @pos
|
173
|
+
@pos += 1
|
174
|
+
s
|
175
|
+
end
|
176
|
+
else
|
177
|
+
def get_byte
|
178
|
+
if @pos >= @string.size
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
s = @string[@pos]
|
183
|
+
@pos += 1
|
184
|
+
s
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def parse(rule=nil)
|
189
|
+
if !rule
|
190
|
+
_root ? true : false
|
191
|
+
else
|
192
|
+
# This is not shared with code_generator.rb so this can be standalone
|
193
|
+
method = rule.gsub("-","_hyphen_")
|
194
|
+
__send__("_#{method}") ? true : false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class LeftRecursive
|
199
|
+
def initialize(detected=false)
|
200
|
+
@detected = detected
|
201
|
+
end
|
202
|
+
|
203
|
+
attr_accessor :detected
|
204
|
+
end
|
205
|
+
|
206
|
+
class MemoEntry
|
207
|
+
def initialize(ans, pos)
|
208
|
+
@ans = ans
|
209
|
+
@pos = pos
|
210
|
+
@uses = 1
|
211
|
+
@result = nil
|
212
|
+
end
|
213
|
+
|
214
|
+
attr_reader :ans, :pos, :uses, :result
|
215
|
+
|
216
|
+
def inc!
|
217
|
+
@uses += 1
|
218
|
+
end
|
219
|
+
|
220
|
+
def move!(ans, pos, result)
|
221
|
+
@ans = ans
|
222
|
+
@pos = pos
|
223
|
+
@result = result
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def external_invoke(other, rule, *args)
|
228
|
+
old_pos = @pos
|
229
|
+
old_string = @string
|
230
|
+
|
231
|
+
@pos = other.pos
|
232
|
+
@string = other.string
|
233
|
+
|
234
|
+
begin
|
235
|
+
if val = __send__(rule, *args)
|
236
|
+
other.pos = @pos
|
237
|
+
else
|
238
|
+
other.set_failed_rule "#{self.class}##{rule}"
|
239
|
+
end
|
240
|
+
val
|
241
|
+
ensure
|
242
|
+
@pos = old_pos
|
243
|
+
@string = old_string
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def apply(rule)
|
248
|
+
if m = @memoizations[rule][@pos]
|
249
|
+
m.inc!
|
250
|
+
|
251
|
+
prev = @pos
|
252
|
+
@pos = m.pos
|
253
|
+
if m.ans.kind_of? LeftRecursive
|
254
|
+
m.ans.detected = true
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
@result = m.result
|
259
|
+
|
260
|
+
return m.ans
|
261
|
+
else
|
262
|
+
lr = LeftRecursive.new(false)
|
263
|
+
m = MemoEntry.new(lr, @pos)
|
264
|
+
@memoizations[rule][@pos] = m
|
265
|
+
start_pos = @pos
|
266
|
+
|
267
|
+
ans = __send__ rule
|
268
|
+
|
269
|
+
m.move! ans, @pos, @result
|
270
|
+
|
271
|
+
# Don't bother trying to grow the left recursion
|
272
|
+
# if it's failing straight away (thus there is no seed)
|
273
|
+
if ans and lr.detected
|
274
|
+
return grow_lr(rule, start_pos, m)
|
275
|
+
else
|
276
|
+
return ans
|
277
|
+
end
|
278
|
+
|
279
|
+
return ans
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def grow_lr(rule, start_pos, m)
|
284
|
+
while true
|
285
|
+
@pos = start_pos
|
286
|
+
@result = m.result
|
287
|
+
|
288
|
+
ans = __send__ rule
|
289
|
+
return nil unless ans
|
290
|
+
|
291
|
+
break if @pos <= m.pos
|
292
|
+
|
293
|
+
m.move! ans, @pos, @result
|
294
|
+
end
|
295
|
+
|
296
|
+
@result = m.result
|
297
|
+
@pos = m.pos
|
298
|
+
return m.ans
|
299
|
+
end
|
300
|
+
|
301
|
+
class RuleInfo
|
302
|
+
def initialize(name, rendered)
|
303
|
+
@name = name
|
304
|
+
@rendered = rendered
|
305
|
+
end
|
306
|
+
|
307
|
+
attr_reader :name, :rendered
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.rule_info(name, rendered)
|
311
|
+
RuleInfo.new(name, rendered)
|
312
|
+
end
|
313
|
+
|
314
|
+
#
|
315
|
+
def setup_foreign_grammar; end
|
316
|
+
|
317
|
+
# root = - value? - end
|
318
|
+
def _root
|
319
|
+
|
320
|
+
_save = self.pos
|
321
|
+
while true # sequence
|
322
|
+
_tmp = apply(:__hyphen_)
|
323
|
+
unless _tmp
|
324
|
+
self.pos = _save
|
325
|
+
break
|
326
|
+
end
|
327
|
+
_save1 = self.pos
|
328
|
+
_tmp = apply(:_value)
|
329
|
+
unless _tmp
|
330
|
+
_tmp = true
|
331
|
+
self.pos = _save1
|
332
|
+
end
|
333
|
+
unless _tmp
|
334
|
+
self.pos = _save
|
335
|
+
break
|
336
|
+
end
|
337
|
+
_tmp = apply(:__hyphen_)
|
338
|
+
unless _tmp
|
339
|
+
self.pos = _save
|
340
|
+
break
|
341
|
+
end
|
342
|
+
_tmp = apply(:_end)
|
343
|
+
unless _tmp
|
344
|
+
self.pos = _save
|
345
|
+
end
|
346
|
+
break
|
347
|
+
end # end sequence
|
348
|
+
|
349
|
+
set_failed_rule :_root unless _tmp
|
350
|
+
return _tmp
|
351
|
+
end
|
352
|
+
|
353
|
+
# end = !.
|
354
|
+
def _end
|
355
|
+
_save = self.pos
|
356
|
+
_tmp = get_byte
|
357
|
+
_tmp = _tmp ? nil : true
|
358
|
+
self.pos = _save
|
359
|
+
set_failed_rule :_end unless _tmp
|
360
|
+
return _tmp
|
361
|
+
end
|
362
|
+
|
363
|
+
# - = (" " | "\t" | "\n")*
|
364
|
+
def __hyphen_
|
365
|
+
while true
|
366
|
+
|
367
|
+
_save1 = self.pos
|
368
|
+
while true # choice
|
369
|
+
_tmp = match_string(" ")
|
370
|
+
break if _tmp
|
371
|
+
self.pos = _save1
|
372
|
+
_tmp = match_string("\t")
|
373
|
+
break if _tmp
|
374
|
+
self.pos = _save1
|
375
|
+
_tmp = match_string("\n")
|
376
|
+
break if _tmp
|
377
|
+
self.pos = _save1
|
378
|
+
break
|
379
|
+
end # end choice
|
380
|
+
|
381
|
+
break unless _tmp
|
382
|
+
end
|
383
|
+
_tmp = true
|
384
|
+
set_failed_rule :__hyphen_ unless _tmp
|
385
|
+
return _tmp
|
386
|
+
end
|
387
|
+
|
388
|
+
# value = (string | number | boolean)
|
389
|
+
def _value
|
390
|
+
|
391
|
+
_save = self.pos
|
392
|
+
while true # choice
|
393
|
+
_tmp = apply(:_string)
|
394
|
+
break if _tmp
|
395
|
+
self.pos = _save
|
396
|
+
_tmp = apply(:_number)
|
397
|
+
break if _tmp
|
398
|
+
self.pos = _save
|
399
|
+
_tmp = apply(:_boolean)
|
400
|
+
break if _tmp
|
401
|
+
self.pos = _save
|
402
|
+
break
|
403
|
+
end # end choice
|
404
|
+
|
405
|
+
set_failed_rule :_value unless _tmp
|
406
|
+
return _tmp
|
407
|
+
end
|
408
|
+
|
409
|
+
# boolean = position (true | false | null | undefined)
|
410
|
+
def _boolean
|
411
|
+
|
412
|
+
_save = self.pos
|
413
|
+
while true # sequence
|
414
|
+
_tmp = apply(:_position)
|
415
|
+
unless _tmp
|
416
|
+
self.pos = _save
|
417
|
+
break
|
418
|
+
end
|
419
|
+
|
420
|
+
_save1 = self.pos
|
421
|
+
while true # choice
|
422
|
+
_tmp = apply(:_true)
|
423
|
+
break if _tmp
|
424
|
+
self.pos = _save1
|
425
|
+
_tmp = apply(:_false)
|
426
|
+
break if _tmp
|
427
|
+
self.pos = _save1
|
428
|
+
_tmp = apply(:_null)
|
429
|
+
break if _tmp
|
430
|
+
self.pos = _save1
|
431
|
+
_tmp = apply(:_undefined)
|
432
|
+
break if _tmp
|
433
|
+
self.pos = _save1
|
434
|
+
break
|
435
|
+
end # end choice
|
436
|
+
|
437
|
+
unless _tmp
|
438
|
+
self.pos = _save
|
439
|
+
end
|
440
|
+
break
|
441
|
+
end # end sequence
|
442
|
+
|
443
|
+
set_failed_rule :_boolean unless _tmp
|
444
|
+
return _tmp
|
445
|
+
end
|
446
|
+
|
447
|
+
# true = "true" {true_value}
|
448
|
+
def _true
|
449
|
+
|
450
|
+
_save = self.pos
|
451
|
+
while true # sequence
|
452
|
+
_tmp = match_string("true")
|
453
|
+
unless _tmp
|
454
|
+
self.pos = _save
|
455
|
+
break
|
456
|
+
end
|
457
|
+
@result = begin; true_value; end
|
458
|
+
_tmp = true
|
459
|
+
unless _tmp
|
460
|
+
self.pos = _save
|
461
|
+
end
|
462
|
+
break
|
463
|
+
end # end sequence
|
464
|
+
|
465
|
+
set_failed_rule :_true unless _tmp
|
466
|
+
return _tmp
|
467
|
+
end
|
468
|
+
|
469
|
+
# false = "false" {false_value}
|
470
|
+
def _false
|
471
|
+
|
472
|
+
_save = self.pos
|
473
|
+
while true # sequence
|
474
|
+
_tmp = match_string("false")
|
475
|
+
unless _tmp
|
476
|
+
self.pos = _save
|
477
|
+
break
|
478
|
+
end
|
479
|
+
@result = begin; false_value; end
|
480
|
+
_tmp = true
|
481
|
+
unless _tmp
|
482
|
+
self.pos = _save
|
483
|
+
end
|
484
|
+
break
|
485
|
+
end # end sequence
|
486
|
+
|
487
|
+
set_failed_rule :_false unless _tmp
|
488
|
+
return _tmp
|
489
|
+
end
|
490
|
+
|
491
|
+
# null = "null" {null_value}
|
492
|
+
def _null
|
493
|
+
|
494
|
+
_save = self.pos
|
495
|
+
while true # sequence
|
496
|
+
_tmp = match_string("null")
|
497
|
+
unless _tmp
|
498
|
+
self.pos = _save
|
499
|
+
break
|
500
|
+
end
|
501
|
+
@result = begin; null_value; end
|
502
|
+
_tmp = true
|
503
|
+
unless _tmp
|
504
|
+
self.pos = _save
|
505
|
+
end
|
506
|
+
break
|
507
|
+
end # end sequence
|
508
|
+
|
509
|
+
set_failed_rule :_null unless _tmp
|
510
|
+
return _tmp
|
511
|
+
end
|
512
|
+
|
513
|
+
# undefined = "undefined" {undefined_value}
|
514
|
+
def _undefined
|
515
|
+
|
516
|
+
_save = self.pos
|
517
|
+
while true # sequence
|
518
|
+
_tmp = match_string("undefined")
|
519
|
+
unless _tmp
|
520
|
+
self.pos = _save
|
521
|
+
break
|
522
|
+
end
|
523
|
+
@result = begin; undefined_value; end
|
524
|
+
_tmp = true
|
525
|
+
unless _tmp
|
526
|
+
self.pos = _save
|
527
|
+
end
|
528
|
+
break
|
529
|
+
end # end sequence
|
530
|
+
|
531
|
+
set_failed_rule :_undefined unless _tmp
|
532
|
+
return _tmp
|
533
|
+
end
|
534
|
+
|
535
|
+
# number = position (real | hex | int)
|
536
|
+
def _number
|
537
|
+
|
538
|
+
_save = self.pos
|
539
|
+
while true # sequence
|
540
|
+
_tmp = apply(:_position)
|
541
|
+
unless _tmp
|
542
|
+
self.pos = _save
|
543
|
+
break
|
544
|
+
end
|
545
|
+
|
546
|
+
_save1 = self.pos
|
547
|
+
while true # choice
|
548
|
+
_tmp = apply(:_real)
|
549
|
+
break if _tmp
|
550
|
+
self.pos = _save1
|
551
|
+
_tmp = apply(:_hex)
|
552
|
+
break if _tmp
|
553
|
+
self.pos = _save1
|
554
|
+
_tmp = apply(:_int)
|
555
|
+
break if _tmp
|
556
|
+
self.pos = _save1
|
557
|
+
break
|
558
|
+
end # end choice
|
559
|
+
|
560
|
+
unless _tmp
|
561
|
+
self.pos = _save
|
562
|
+
end
|
563
|
+
break
|
564
|
+
end # end sequence
|
565
|
+
|
566
|
+
set_failed_rule :_number unless _tmp
|
567
|
+
return _tmp
|
568
|
+
end
|
569
|
+
|
570
|
+
# hexdigits = /[0-9A-Fa-f]/
|
571
|
+
def _hexdigits
|
572
|
+
_tmp = scan(/\A(?-mix:[0-9A-Fa-f])/)
|
573
|
+
set_failed_rule :_hexdigits unless _tmp
|
574
|
+
return _tmp
|
575
|
+
end
|
576
|
+
|
577
|
+
# hex = "0x" < hexdigits+ > {hexadecimal(text)}
|
578
|
+
def _hex
|
579
|
+
|
580
|
+
_save = self.pos
|
581
|
+
while true # sequence
|
582
|
+
_tmp = match_string("0x")
|
583
|
+
unless _tmp
|
584
|
+
self.pos = _save
|
585
|
+
break
|
586
|
+
end
|
587
|
+
_text_start = self.pos
|
588
|
+
_save1 = self.pos
|
589
|
+
_tmp = apply(:_hexdigits)
|
590
|
+
if _tmp
|
591
|
+
while true
|
592
|
+
_tmp = apply(:_hexdigits)
|
593
|
+
break unless _tmp
|
594
|
+
end
|
595
|
+
_tmp = true
|
596
|
+
else
|
597
|
+
self.pos = _save1
|
598
|
+
end
|
599
|
+
if _tmp
|
600
|
+
text = get_text(_text_start)
|
601
|
+
end
|
602
|
+
unless _tmp
|
603
|
+
self.pos = _save
|
604
|
+
break
|
605
|
+
end
|
606
|
+
@result = begin; hexadecimal(text); end
|
607
|
+
_tmp = true
|
608
|
+
unless _tmp
|
609
|
+
self.pos = _save
|
610
|
+
end
|
611
|
+
break
|
612
|
+
end # end sequence
|
613
|
+
|
614
|
+
set_failed_rule :_hex unless _tmp
|
615
|
+
return _tmp
|
616
|
+
end
|
617
|
+
|
618
|
+
# digits = ("0" | /[1-9]/ /[0-9]/*)
|
619
|
+
def _digits
|
620
|
+
|
621
|
+
_save = self.pos
|
622
|
+
while true # choice
|
623
|
+
_tmp = match_string("0")
|
624
|
+
break if _tmp
|
625
|
+
self.pos = _save
|
626
|
+
|
627
|
+
_save1 = self.pos
|
628
|
+
while true # sequence
|
629
|
+
_tmp = scan(/\A(?-mix:[1-9])/)
|
630
|
+
unless _tmp
|
631
|
+
self.pos = _save1
|
632
|
+
break
|
633
|
+
end
|
634
|
+
while true
|
635
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
636
|
+
break unless _tmp
|
637
|
+
end
|
638
|
+
_tmp = true
|
639
|
+
unless _tmp
|
640
|
+
self.pos = _save1
|
641
|
+
end
|
642
|
+
break
|
643
|
+
end # end sequence
|
644
|
+
|
645
|
+
break if _tmp
|
646
|
+
self.pos = _save
|
647
|
+
break
|
648
|
+
end # end choice
|
649
|
+
|
650
|
+
set_failed_rule :_digits unless _tmp
|
651
|
+
return _tmp
|
652
|
+
end
|
653
|
+
|
654
|
+
# int = < digits > {number(text)}
|
655
|
+
def _int
|
656
|
+
|
657
|
+
_save = self.pos
|
658
|
+
while true # sequence
|
659
|
+
_text_start = self.pos
|
660
|
+
_tmp = apply(:_digits)
|
661
|
+
if _tmp
|
662
|
+
text = get_text(_text_start)
|
663
|
+
end
|
664
|
+
unless _tmp
|
665
|
+
self.pos = _save
|
666
|
+
break
|
667
|
+
end
|
668
|
+
@result = begin; number(text); end
|
669
|
+
_tmp = true
|
670
|
+
unless _tmp
|
671
|
+
self.pos = _save
|
672
|
+
end
|
673
|
+
break
|
674
|
+
end # end sequence
|
675
|
+
|
676
|
+
set_failed_rule :_int unless _tmp
|
677
|
+
return _tmp
|
678
|
+
end
|
679
|
+
|
680
|
+
# real = < digits "." digits ("e" /[-+]/? /[0-9]/+)? > {number(text)}
|
681
|
+
def _real
|
682
|
+
|
683
|
+
_save = self.pos
|
684
|
+
while true # sequence
|
685
|
+
_text_start = self.pos
|
686
|
+
|
687
|
+
_save1 = self.pos
|
688
|
+
while true # sequence
|
689
|
+
_tmp = apply(:_digits)
|
690
|
+
unless _tmp
|
691
|
+
self.pos = _save1
|
692
|
+
break
|
693
|
+
end
|
694
|
+
_tmp = match_string(".")
|
695
|
+
unless _tmp
|
696
|
+
self.pos = _save1
|
697
|
+
break
|
698
|
+
end
|
699
|
+
_tmp = apply(:_digits)
|
700
|
+
unless _tmp
|
701
|
+
self.pos = _save1
|
702
|
+
break
|
703
|
+
end
|
704
|
+
_save2 = self.pos
|
705
|
+
|
706
|
+
_save3 = self.pos
|
707
|
+
while true # sequence
|
708
|
+
_tmp = match_string("e")
|
709
|
+
unless _tmp
|
710
|
+
self.pos = _save3
|
711
|
+
break
|
712
|
+
end
|
713
|
+
_save4 = self.pos
|
714
|
+
_tmp = scan(/\A(?-mix:[-+])/)
|
715
|
+
unless _tmp
|
716
|
+
_tmp = true
|
717
|
+
self.pos = _save4
|
718
|
+
end
|
719
|
+
unless _tmp
|
720
|
+
self.pos = _save3
|
721
|
+
break
|
722
|
+
end
|
723
|
+
_save5 = self.pos
|
724
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
725
|
+
if _tmp
|
726
|
+
while true
|
727
|
+
_tmp = scan(/\A(?-mix:[0-9])/)
|
728
|
+
break unless _tmp
|
729
|
+
end
|
730
|
+
_tmp = true
|
731
|
+
else
|
732
|
+
self.pos = _save5
|
733
|
+
end
|
734
|
+
unless _tmp
|
735
|
+
self.pos = _save3
|
736
|
+
end
|
737
|
+
break
|
738
|
+
end # end sequence
|
739
|
+
|
740
|
+
unless _tmp
|
741
|
+
_tmp = true
|
742
|
+
self.pos = _save2
|
743
|
+
end
|
744
|
+
unless _tmp
|
745
|
+
self.pos = _save1
|
746
|
+
end
|
747
|
+
break
|
748
|
+
end # end sequence
|
749
|
+
|
750
|
+
if _tmp
|
751
|
+
text = get_text(_text_start)
|
752
|
+
end
|
753
|
+
unless _tmp
|
754
|
+
self.pos = _save
|
755
|
+
break
|
756
|
+
end
|
757
|
+
@result = begin; number(text); end
|
758
|
+
_tmp = true
|
759
|
+
unless _tmp
|
760
|
+
self.pos = _save
|
761
|
+
end
|
762
|
+
break
|
763
|
+
end # end sequence
|
764
|
+
|
765
|
+
set_failed_rule :_real unless _tmp
|
766
|
+
return _tmp
|
767
|
+
end
|
768
|
+
|
769
|
+
# string = position "\"" < /[^\\"]*/ > "\"" {string_value(text)}
|
770
|
+
def _string
|
771
|
+
|
772
|
+
_save = self.pos
|
773
|
+
while true # sequence
|
774
|
+
_tmp = apply(:_position)
|
775
|
+
unless _tmp
|
776
|
+
self.pos = _save
|
777
|
+
break
|
778
|
+
end
|
779
|
+
_tmp = match_string("\"")
|
780
|
+
unless _tmp
|
781
|
+
self.pos = _save
|
782
|
+
break
|
783
|
+
end
|
784
|
+
_text_start = self.pos
|
785
|
+
_tmp = scan(/\A(?-mix:[^\\"]*)/)
|
786
|
+
if _tmp
|
787
|
+
text = get_text(_text_start)
|
788
|
+
end
|
789
|
+
unless _tmp
|
790
|
+
self.pos = _save
|
791
|
+
break
|
792
|
+
end
|
793
|
+
_tmp = match_string("\"")
|
794
|
+
unless _tmp
|
795
|
+
self.pos = _save
|
796
|
+
break
|
797
|
+
end
|
798
|
+
@result = begin; string_value(text); end
|
799
|
+
_tmp = true
|
800
|
+
unless _tmp
|
801
|
+
self.pos = _save
|
802
|
+
end
|
803
|
+
break
|
804
|
+
end # end sequence
|
805
|
+
|
806
|
+
set_failed_rule :_string unless _tmp
|
807
|
+
return _tmp
|
808
|
+
end
|
809
|
+
|
810
|
+
# line = { current_line }
|
811
|
+
def _line
|
812
|
+
@result = begin; current_line ; end
|
813
|
+
_tmp = true
|
814
|
+
set_failed_rule :_line unless _tmp
|
815
|
+
return _tmp
|
816
|
+
end
|
817
|
+
|
818
|
+
# column = { current_column }
|
819
|
+
def _column
|
820
|
+
@result = begin; current_column ; end
|
821
|
+
_tmp = true
|
822
|
+
set_failed_rule :_column unless _tmp
|
823
|
+
return _tmp
|
824
|
+
end
|
825
|
+
|
826
|
+
# position = line:l column:c { position(l, c) }
|
827
|
+
def _position
|
828
|
+
|
829
|
+
_save = self.pos
|
830
|
+
while true # sequence
|
831
|
+
_tmp = apply(:_line)
|
832
|
+
l = @result
|
833
|
+
unless _tmp
|
834
|
+
self.pos = _save
|
835
|
+
break
|
836
|
+
end
|
837
|
+
_tmp = apply(:_column)
|
838
|
+
c = @result
|
839
|
+
unless _tmp
|
840
|
+
self.pos = _save
|
841
|
+
break
|
842
|
+
end
|
843
|
+
@result = begin; position(l, c) ; end
|
844
|
+
_tmp = true
|
845
|
+
unless _tmp
|
846
|
+
self.pos = _save
|
847
|
+
end
|
848
|
+
break
|
849
|
+
end # end sequence
|
850
|
+
|
851
|
+
set_failed_rule :_position unless _tmp
|
852
|
+
return _tmp
|
853
|
+
end
|
854
|
+
|
855
|
+
Rules = {}
|
856
|
+
Rules[:_root] = rule_info("root", "- value? - end")
|
857
|
+
Rules[:_end] = rule_info("end", "!.")
|
858
|
+
Rules[:__hyphen_] = rule_info("-", "(\" \" | \"\\t\" | \"\\n\")*")
|
859
|
+
Rules[:_value] = rule_info("value", "(string | number | boolean)")
|
860
|
+
Rules[:_boolean] = rule_info("boolean", "position (true | false | null | undefined)")
|
861
|
+
Rules[:_true] = rule_info("true", "\"true\" {true_value}")
|
862
|
+
Rules[:_false] = rule_info("false", "\"false\" {false_value}")
|
863
|
+
Rules[:_null] = rule_info("null", "\"null\" {null_value}")
|
864
|
+
Rules[:_undefined] = rule_info("undefined", "\"undefined\" {undefined_value}")
|
865
|
+
Rules[:_number] = rule_info("number", "position (real | hex | int)")
|
866
|
+
Rules[:_hexdigits] = rule_info("hexdigits", "/[0-9A-Fa-f]/")
|
867
|
+
Rules[:_hex] = rule_info("hex", "\"0x\" < hexdigits+ > {hexadecimal(text)}")
|
868
|
+
Rules[:_digits] = rule_info("digits", "(\"0\" | /[1-9]/ /[0-9]/*)")
|
869
|
+
Rules[:_int] = rule_info("int", "< digits > {number(text)}")
|
870
|
+
Rules[:_real] = rule_info("real", "< digits \".\" digits (\"e\" /[-+]/? /[0-9]/+)? > {number(text)}")
|
871
|
+
Rules[:_string] = rule_info("string", "position \"\\\"\" < /[^\\\\\"]*/ > \"\\\"\" {string_value(text)}")
|
872
|
+
Rules[:_line] = rule_info("line", "{ current_line }")
|
873
|
+
Rules[:_column] = rule_info("column", "{ current_column }")
|
874
|
+
Rules[:_position] = rule_info("position", "line:l column:c { position(l, c) }")
|
875
|
+
end
|