meta_compile 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -4
- data/Rakefile +98 -50
- data/bin/meta_compile +295 -195
- data/bin/metac_minimal +231 -0
- data/bin/metac_readable +331 -0
- data/bin/{metacomp_re → metac_regexp} +0 -0
- data/meta_compile.gemspec +1 -1
- data/syntaxes/meta_to_ruby.meta +66 -43
- metadata +5 -3
data/README.md
CHANGED
@@ -3,7 +3,7 @@ meta_compile
|
|
3
3
|
|
4
4
|
A meta compilation framework for Ruby à la [Meta-II by Val Schorre](http://ibm-1401.info/Meta-II-schorre.pdf).
|
5
5
|
|
6
|
-
This uses a [C version of Meta-II](https://github.com/impeachgod/meta) developed by Long Nguyen to bootstrap a Ruby version from a [fully self-contained, 26 (non-empty) line specification file](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby_minimal.meta). The [generated Ruby Meta-II compiler file](https://github.com/robertfeldt/meta_compile/blob/master/bin/
|
6
|
+
This uses a [C version of Meta-II](https://github.com/impeachgod/meta) developed by Long Nguyen to bootstrap a Ruby version from a [fully self-contained, 26 (non-empty) line specification file](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby_minimal.meta). The [generated Ruby Meta-II compiler file](https://github.com/robertfeldt/meta_compile/blob/master/bin/metac_minimal) is 231 lines of Ruby code. A more readable and commented version of the specification file [is available here](https://raw.github.com/robertfeldt/meta_compile/master/syntaxes/meta_to_ruby.meta). It also has better error handling both when used from the command line and when compiling (but it is longer, 74 loc for the spec and 331 loc for the [generated compiler](https://github.com/robertfeldt/meta_compile/blob/master/bin/metac_readable)).
|
7
7
|
|
8
8
|
Install
|
9
9
|
-------
|
@@ -45,7 +45,7 @@ and this should convince us:
|
|
45
45
|
|
46
46
|
Limitations
|
47
47
|
-----------
|
48
|
-
+
|
48
|
+
+ Rudimentary error handling/reports while compiling/parsing
|
49
49
|
|
50
50
|
Ok, now what?
|
51
51
|
-------------
|
@@ -62,11 +62,11 @@ Let's use this to compile programs which can only contain assignments of numbers
|
|
62
62
|
|
63
63
|
The new thing here compared to the original Meta-II syntax is the two Regexp's (in as and ex1). First we need to bootstrap the meta compiler that accepts regexps:
|
64
64
|
|
65
|
-
rake
|
65
|
+
rake boot_regexp
|
66
66
|
|
67
67
|
Then we create a compiler for the assignments syntax:
|
68
68
|
|
69
|
-
ruby bin/
|
69
|
+
ruby bin/metac_regexp syntaxes/assignments.meta > tas.rb
|
70
70
|
|
71
71
|
We now have a compiler for assignments and if we apply it [to the file](https://raw.github.com/robertfeldt/meta_compile/master/inputs/assignments.input1):
|
72
72
|
|
data/Rakefile
CHANGED
@@ -9,18 +9,19 @@ def pexec(str)
|
|
9
9
|
end
|
10
10
|
|
11
11
|
desc "Bootstrap our version of Long Nguyen's C version of Meta-II"
|
12
|
-
task :
|
13
|
-
Dir.chdir "bootstrap"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
task :boot_c do
|
13
|
+
Dir.chdir "bootstrap" do
|
14
|
+
puts "1. Build bootstrap compiler"
|
15
|
+
pexec "gcc -o bootstrapped_c bootstrap.c"
|
16
|
+
puts "2. Use the bootstrapped meta compiler to compile the meta_to_c.meta description of itself"
|
17
|
+
pexec "./bootstrapped_c meta_to_c.meta meta_compiler_from_boostrapped_c.c"
|
18
|
+
puts "3. Build the compiler we created from the meta description"
|
19
|
+
pexec "gcc -o meta_c meta_compiler_from_boostrapped_c.c"
|
20
|
+
puts "4. Now use the generated compiler to generate itself"
|
21
|
+
pexec "./meta_c meta_to_c.meta meta_compiler.c"
|
22
|
+
puts "5. Now ensure the compilers are exactly the same"
|
23
|
+
pexec "diff meta_compiler_from_boostrapped_c.c meta_compiler.c"
|
24
|
+
end
|
24
25
|
end
|
25
26
|
|
26
27
|
# Non-empty Lines of Code calc
|
@@ -28,42 +29,49 @@ def loc(filename)
|
|
28
29
|
File.readlines(filename).map {|l| l.strip.length==0?nil:l}.compact.length
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
def bootstrap_from_c(syntaxFile)
|
33
|
+
if syntaxFile =~ /^syntaxes/
|
34
|
+
syntaxf = File.join("..", syntaxFile)
|
35
|
+
else
|
36
|
+
syntaxf = File.join("..", "syntaxes", syntaxFile)
|
37
|
+
end
|
33
38
|
Dir.chdir("bootstrap") do
|
34
39
|
puts "1. Build bootstrap compiler"
|
35
40
|
pexec "gcc -o bootstrapped_c bootstrap.c"
|
36
41
|
puts "2. Use the c meta compiler to compile the meta_to_ruby_minimal.meta description"
|
37
|
-
pexec "./bootstrapped_c
|
42
|
+
pexec "./bootstrapped_c #{syntaxf} compile_to_ruby.c"
|
38
43
|
puts "3. Build the stepping stone ruby compiler we created from the meta description"
|
39
44
|
pexec "gcc -o meta_r compile_to_ruby.c"
|
40
45
|
puts "4. Now use the generated stepping stone compiler to generate a ruby compiler for the ruby meta syntax"
|
41
|
-
pexec "./meta_r
|
46
|
+
pexec "./meta_r #{syntaxf} meta_ruby_compiler_from_c.rb"
|
42
47
|
puts "5. Run the generated ruby meta compiler to a ruby version"
|
43
|
-
pexec "ruby -I. meta_ruby_compiler_from_c.rb
|
48
|
+
pexec "ruby -I. meta_ruby_compiler_from_c.rb #{syntaxf} > meta_ruby_compiler.rb"
|
44
49
|
puts "6. Ruby version differ since it has single instead of double quotes around strings"
|
45
50
|
#pexec "diff meta_ruby_compiler_from_c.rb meta_ruby_compiler.rb"
|
46
51
|
puts "7. But we can generate again and ensure it is a meta compiler"
|
47
|
-
pexec "ruby -I. meta_ruby_compiler.rb
|
52
|
+
pexec "ruby -I. meta_ruby_compiler.rb #{syntaxf} > meta_ruby_compiler2.rb"
|
48
53
|
pexec "diff meta_ruby_compiler.rb meta_ruby_compiler2.rb"
|
49
54
|
puts "8. One more round just to show off... :)"
|
50
|
-
pexec "ruby -I. meta_ruby_compiler2.rb
|
55
|
+
pexec "ruby -I. meta_ruby_compiler2.rb #{syntaxf} > meta_ruby_compiler3.rb"
|
51
56
|
pexec "diff meta_ruby_compiler.rb meta_ruby_compiler3.rb"
|
52
|
-
puts "Summary:\nCreated a #{loc('meta_ruby_compiler.rb')} line meta-II meta compiler from a #{loc(
|
57
|
+
puts "Summary:\nCreated a #{loc('meta_ruby_compiler.rb')} line meta-II meta compiler from a #{loc(syntaxf)} line meta-II spec\n\n"
|
58
|
+
ensure_is_meta "meta_ruby_compiler.rb", syntaxf, "../bin/meta_compile"
|
53
59
|
end
|
54
60
|
end
|
55
61
|
|
56
62
|
# Bootstrap a syntax from the meta_compile compiler
|
57
|
-
def
|
58
|
-
|
59
|
-
|
63
|
+
def bootstrap_from_ruby(syntaxFile, targetName = "tgen.rb")
|
64
|
+
syntaxf = File.join("syntaxes", syntaxFile)
|
65
|
+
# Assumes the ruby version has been bootstrapped from C and is installed as meta_compile
|
66
|
+
pexec "ruby bin/meta_compile #{syntaxf} > #{targetName}"
|
67
|
+
ensure_is_meta(targetName, syntaxf, targetName)
|
60
68
|
end
|
61
69
|
|
62
70
|
def diff_files(f1, f2)
|
63
71
|
File.read(f1) != File.read(f2)
|
64
72
|
end
|
65
73
|
|
66
|
-
def ensure_is_meta(generatedFile, specFile)
|
74
|
+
def ensure_is_meta(generatedFile, specFile, saveAsTarget = nil)
|
67
75
|
pexec "ruby #{generatedFile} #{specFile} > t.rb"
|
68
76
|
pexec "ruby t.rb #{specFile} > t2.rb"
|
69
77
|
# Obviously we should now be able to go on and on... :)
|
@@ -74,35 +82,74 @@ def ensure_is_meta(generatedFile, specFile)
|
|
74
82
|
exit(-1)
|
75
83
|
else
|
76
84
|
puts "YES #{generatedFile} is meta!!! (generated from #{specFile})"
|
85
|
+
if saveAsTarget
|
86
|
+
FileUtils.mv("t2.rb", saveAsTarget)
|
87
|
+
puts "Saved bootstrapped file as #{saveAsTarget}"
|
88
|
+
end
|
89
|
+
return true
|
77
90
|
end
|
78
91
|
ensure
|
79
92
|
FileUtils.rm_f Dir.glob("t*.rb")
|
80
93
|
end
|
81
94
|
end
|
82
95
|
|
83
|
-
|
84
|
-
|
85
|
-
FileUtils.cp "bootstrap/meta_ruby_compiler.rb", "bin/meta_compile"
|
96
|
+
def create_main_binary(sourceBinary)
|
97
|
+
FileUtils.cp sourceBinary, "bin/meta_compile"
|
86
98
|
FileUtils.chmod 0755, "bin/meta_compile"
|
87
|
-
puts "Created binary in bin/meta_compile"
|
88
|
-
end
|
89
|
-
|
90
|
-
desc "Ensure it is a meta compiler"
|
91
|
-
task :ensure_meta => [:make_bin] do
|
92
|
-
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby_minimal.meta"
|
99
|
+
puts "Created main binary in bin/meta_compile from #{sourceBinary}"
|
93
100
|
end
|
94
101
|
|
95
102
|
def bootstrap_with_stepping_stone(syntaxFile, target)
|
96
103
|
syntaxf = File.join("syntaxes", syntaxFile)
|
97
104
|
stepstonef = File.join("syntaxes", "stepping_stone_" + syntaxFile)
|
98
|
-
pexec "meta_compile #{stepstonef} > tgen_stepstone.rb"
|
105
|
+
pexec "ruby bin/meta_compile #{stepstonef} > tgen_stepstone.rb"
|
99
106
|
pexec "ruby tgen_stepstone.rb #{syntaxf} > #{target}"
|
100
|
-
ensure_is_meta(target, syntaxf)
|
107
|
+
ensure_is_meta(target, syntaxf, target)
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Bootstrap the Ruby meta compiler (uses the minimal syntax)"
|
111
|
+
task :boot do
|
112
|
+
bootstrap_from_c "meta_to_ruby_minimal.meta"
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "Bootstrap and set binary file permissions"
|
116
|
+
task :bootstrap => :boot do
|
117
|
+
FileUtils.chmod 0755, "bin/meta_compile"
|
118
|
+
end
|
119
|
+
|
120
|
+
desc "Bootstrap the minimal Ruby meta compiler (from the Ruby meta compiler)"
|
121
|
+
task :boot_minimal => :bootstrap do
|
122
|
+
bootstrap_from_ruby "meta_to_ruby_minimal.meta", "bin/metac_minimal"
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "Make main gem binary from the minimal ruby meta compiler"
|
126
|
+
task :make_bin_minimal => :boot_minimal do
|
127
|
+
create_main_binary "bin/metac_minimal"
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "Ensure minimal ruby meta it is a meta compiler"
|
131
|
+
task :ensure_meta_minimal => :make_bin_minimal do
|
132
|
+
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby_minimal.meta"
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "Bootstrap the readable meta compiler (from the Ruby meta compiler)"
|
136
|
+
task :boot_readable => :make_bin_minimal do
|
137
|
+
bootstrap_from_ruby "meta_to_ruby.meta", "bin/metac_readable"
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "Make main gem binary from the readable ruby meta compiler"
|
141
|
+
task :make_bin_readable => :boot_readable do
|
142
|
+
create_main_binary "bin/metac_readable"
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "Ensure readable ruby meta it is a meta compiler"
|
146
|
+
task :ensure_meta_readable => :make_bin_readable do
|
147
|
+
ensure_is_meta "bin/meta_compile", "syntaxes/meta_to_ruby.meta"
|
101
148
|
end
|
102
149
|
|
103
150
|
desc "Bootstrap the meta compiler that accepts regexps"
|
104
|
-
task :
|
105
|
-
bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/
|
151
|
+
task :boot_regexp => :make_bin_minimal do
|
152
|
+
bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/metac_regexp")
|
106
153
|
end
|
107
154
|
|
108
155
|
def run_example(compiler, example)
|
@@ -112,8 +159,8 @@ def run_example(compiler, example)
|
|
112
159
|
pexec "ruby #{compiler} #{example}"
|
113
160
|
end
|
114
161
|
|
115
|
-
def
|
116
|
-
pexec "ruby bin/
|
162
|
+
def metac_regexp_run_examples(syntaxFile, *inputs)
|
163
|
+
pexec "ruby bin/metac_regexp #{syntaxFile} > tas.rb"
|
117
164
|
inputs.each do |i|
|
118
165
|
run_example("tas.rb", "inputs/#{i}")
|
119
166
|
end
|
@@ -121,22 +168,21 @@ end
|
|
121
168
|
|
122
169
|
desc "Compile all inputs for the assignments syntax"
|
123
170
|
task :ex_ass do
|
124
|
-
|
171
|
+
metac_regexp_run_examples "syntaxes/assignments.meta", "assignments.input1"
|
125
172
|
end
|
126
173
|
|
127
174
|
desc "Update line counts in README.template.md to make README.md"
|
128
|
-
task :update => [:
|
129
|
-
|
130
|
-
|
131
|
-
readme =
|
132
|
-
readme = readme.gsub("%%
|
175
|
+
task :update => [:boot_minimal, :boot_regexp, :boot_readable] do
|
176
|
+
readme = File.read("README.template.md")
|
177
|
+
readme = readme.gsub("%%RMetaII_SPEC_LOC%%", loc('syntaxes/meta_to_ruby_minimal.meta').to_s)
|
178
|
+
readme = readme.gsub("%%RMetaII_COMPILER_LOC%%", loc('bin/metac_minimal').to_s)
|
179
|
+
readme = readme.gsub("%%RMetaII_READABLE_SPEC_LOC%%", loc('syntaxes/meta_to_ruby.meta').to_s)
|
180
|
+
readme = readme.gsub("%%RMetaII_READABLE_COMPILER_LOC%%", loc('bin/metac_readable').to_s)
|
133
181
|
File.open("README.md", "w") {|f| f.puts readme}
|
134
182
|
end
|
135
183
|
|
136
|
-
task :default => :ensure_meta
|
137
|
-
|
138
184
|
desc "Build the gem"
|
139
|
-
task :build_gem => [:
|
185
|
+
task :build_gem => [:update, :make_bin_readable] do
|
140
186
|
Rake::Task["clean"].invoke
|
141
187
|
FileUtils.rm_f Dir.glob("meta_compile-*.gem")
|
142
188
|
pexec "gem build meta_compile.gemspec"
|
@@ -148,7 +194,7 @@ task :install_gem => [:build_gem] do
|
|
148
194
|
end
|
149
195
|
|
150
196
|
desc "Deploy gem"
|
151
|
-
task :deploy =>
|
197
|
+
task :deploy => :build_gem do
|
152
198
|
pexec "gem push meta_compile*.gem"
|
153
199
|
end
|
154
200
|
|
@@ -164,4 +210,6 @@ task :clobber => [:clean] do
|
|
164
210
|
FileUtils.rm_f %w{meta_ruby_compiler.rb meta_compile*.gem}
|
165
211
|
end
|
166
212
|
FileUtils.rm_f Dir.glob("meta_compile-*.gem")
|
167
|
-
end
|
213
|
+
end
|
214
|
+
|
215
|
+
task :default => :make_bin_readable
|
data/bin/meta_compile
CHANGED
@@ -1,231 +1,331 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require "strscan"
|
3
|
-
class
|
4
|
-
|
5
|
-
def
|
6
|
-
|
7
|
-
|
3
|
+
class StringScanner
|
4
|
+
# Add scan for string since base StringScanner lack that...
|
5
|
+
def scan_str(string)
|
6
|
+
return nil unless self.peek(string.length) == string
|
7
|
+
self.pos += string.length # Advance the position in the input string
|
8
|
+
return string
|
9
|
+
end
|
10
|
+
alias :old_scan :scan
|
11
|
+
def scan(strOrRegexp)
|
12
|
+
String === strOrRegexp ? scan_str(strOrRegexp) : old_scan(strOrRegexp)
|
13
|
+
end
|
8
14
|
end
|
15
|
+
class MetaCompiler_program
|
16
|
+
$compiler_class = self # Save class in global var for later reference below
|
17
|
+
def compile_string(string, outFile)
|
18
|
+
@in, @out = StringScanner.new(string), outFile
|
19
|
+
compile_program # call the main compile method to start compiling
|
20
|
+
end
|
21
|
+
# Scan for a string or regexp and update state based on match. Skips leading whitespace.
|
22
|
+
def scan strOrRegexp
|
23
|
+
@in.scan /\s*/ # Skip whitespace
|
24
|
+
@match = @in.scan strOrRegexp
|
25
|
+
# Since nil is same as false in Ruby we can set the flag to the matched token
|
26
|
+
@last_matched_token = @match if @match # Update last matched only if a token was matched
|
27
|
+
end
|
28
|
+
def report_error
|
29
|
+
pre_lines = @in.string[0, @in.pos].split("\n") # lines of input up to current position
|
30
|
+
post_lines = @in.rest.split("\n") # lines of input after current position
|
31
|
+
message = "PARSE ERROR at line #{pre_lines.length}:\n " + pre_lines.last.inspect + " @ "
|
32
|
+
message += post_lines.first.inspect
|
33
|
+
message += "\n Last matched token: #{@last_matched_token}"
|
34
|
+
raise message
|
35
|
+
end
|
36
|
+
def self.compile_file(inFile, out = nil)
|
37
|
+
outfh = (out == nil ? STDOUT : File.open(out, "w"))
|
38
|
+
self.new.compile_string(File.read(inFile), outfh)
|
39
|
+
outfh.close if out == nil
|
40
|
+
end
|
9
41
|
def compile_outarg
|
10
42
|
begin
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
@o.print "\n"
|
43
|
+
scan '$'
|
44
|
+
if @match
|
45
|
+
@out.print '@out.print @match # Print last matched token on output stream' # Print literal string on output stream
|
46
|
+
@out.print "\n" # Print newline on output stream
|
16
47
|
end
|
17
|
-
break if @
|
18
|
-
|
19
|
-
if @
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
48
|
+
break if @match
|
49
|
+
scan /\047[^\047]*\047/
|
50
|
+
if @match
|
51
|
+
@out.print '@out.print ' # Print literal string on output stream
|
52
|
+
@out.print @match # Print last matched token on output stream
|
53
|
+
@out.print ' # Print literal string on output stream' # Print literal string on output stream
|
54
|
+
@out.print "\n" # Print newline on output stream
|
23
55
|
end
|
24
|
-
end while false
|
56
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
25
57
|
end
|
26
58
|
def compile_out
|
27
59
|
begin
|
28
|
-
|
29
|
-
|
30
|
-
if @f
|
60
|
+
scan '<'
|
61
|
+
if @match
|
31
62
|
begin
|
32
|
-
compile_outarg
|
33
|
-
end while @
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@
|
40
|
-
|
41
|
-
end
|
42
|
-
end while false
|
63
|
+
compile_outarg # Call the method for a rule
|
64
|
+
end while @match # Loop while there is a match
|
65
|
+
@match = true # Since also zero matches is ok set flag here.
|
66
|
+
report_error() unless @match
|
67
|
+
scan '>'
|
68
|
+
report_error() unless @match
|
69
|
+
@out.print '@out.print "\n" # Print newline on output stream' # Print literal string on output stream
|
70
|
+
@out.print "\n" # Print newline on output stream
|
71
|
+
end
|
72
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
43
73
|
end
|
44
74
|
def compile_exp3
|
45
75
|
begin
|
46
|
-
|
47
|
-
if @
|
48
|
-
@
|
49
|
-
@
|
50
|
-
@
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
@
|
57
|
-
@
|
58
|
-
@
|
59
|
-
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
@
|
64
|
-
@
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
@
|
71
|
-
|
72
|
-
if @
|
73
|
-
|
74
|
-
@
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@
|
83
|
-
@
|
84
|
-
|
85
|
-
end
|
86
|
-
break if @
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
@
|
95
|
-
@
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
compile_exp3
|
100
|
-
raise("error at: " + @i.rest.split("\n")[0]) if !@f
|
101
|
-
@o.print 'end while @f'
|
102
|
-
@o.print "\n"
|
103
|
-
@o.print '@f = true'
|
104
|
-
@o.print "\n"
|
105
|
-
end
|
106
|
-
end while false
|
76
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
77
|
+
if @match
|
78
|
+
@out.print 'compile_' # Print literal string on output stream
|
79
|
+
@out.print @match # Print last matched token on output stream
|
80
|
+
@out.print ' # Call the method for a rule' # Print literal string on output stream
|
81
|
+
@out.print "\n" # Print newline on output stream
|
82
|
+
end
|
83
|
+
break if @match
|
84
|
+
scan /\047[^\047]*\047/
|
85
|
+
if @match
|
86
|
+
@out.print 'scan ' # Print literal string on output stream
|
87
|
+
@out.print @match # Print last matched token on output stream
|
88
|
+
@out.print "\n" # Print newline on output stream
|
89
|
+
end
|
90
|
+
break if @match
|
91
|
+
scan '.id'
|
92
|
+
if @match
|
93
|
+
@out.print 'scan /[A-Za-z]+[A-Za-z0-9_]+/' # Print literal string on output stream
|
94
|
+
@out.print "\n" # Print newline on output stream
|
95
|
+
end
|
96
|
+
break if @match
|
97
|
+
scan '.string'
|
98
|
+
if @match
|
99
|
+
@out.print 'scan /\047[^\047]*\047/' # Print literal string on output stream
|
100
|
+
@out.print "\n" # Print newline on output stream
|
101
|
+
end
|
102
|
+
break if @match
|
103
|
+
scan '('
|
104
|
+
if @match
|
105
|
+
compile_exp1 # Call the method for a rule
|
106
|
+
report_error() unless @match
|
107
|
+
scan ')'
|
108
|
+
report_error() unless @match
|
109
|
+
end
|
110
|
+
break if @match
|
111
|
+
scan '.e'
|
112
|
+
if @match
|
113
|
+
@out.print '@match = true # .e means empty so always matches => set flag.' # Print literal string on output stream
|
114
|
+
@out.print "\n" # Print newline on output stream
|
115
|
+
end
|
116
|
+
break if @match
|
117
|
+
scan '*'
|
118
|
+
if @match
|
119
|
+
@out.print 'begin' # Print literal string on output stream
|
120
|
+
@out.print "\n" # Print newline on output stream
|
121
|
+
compile_exp3 # Call the method for a rule
|
122
|
+
report_error() unless @match
|
123
|
+
@out.print 'end while @match # Loop while there is a match' # Print literal string on output stream
|
124
|
+
@out.print "\n" # Print newline on output stream
|
125
|
+
@out.print '@match = true # Since also zero matches is ok set flag here.' # Print literal string on output stream
|
126
|
+
@out.print "\n" # Print newline on output stream
|
127
|
+
end
|
128
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
107
129
|
end
|
108
130
|
def compile_exp2
|
109
131
|
begin
|
110
132
|
begin
|
111
|
-
compile_exp3
|
112
|
-
if @
|
113
|
-
@
|
114
|
-
@
|
115
|
-
end
|
116
|
-
break if @
|
117
|
-
compile_out
|
118
|
-
if @
|
119
|
-
@
|
120
|
-
@
|
121
|
-
end
|
122
|
-
end while false
|
123
|
-
if @
|
133
|
+
compile_exp3 # Call the method for a rule
|
134
|
+
if @match
|
135
|
+
@out.print 'if @match' # Print literal string on output stream
|
136
|
+
@out.print "\n" # Print newline on output stream
|
137
|
+
end
|
138
|
+
break if @match
|
139
|
+
compile_out # Call the method for a rule
|
140
|
+
if @match
|
141
|
+
@out.print 'if true' # Print literal string on output stream
|
142
|
+
@out.print "\n" # Print newline on output stream
|
143
|
+
end
|
144
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
145
|
+
if @match
|
124
146
|
begin
|
125
147
|
begin
|
126
|
-
compile_exp3
|
127
|
-
if @
|
128
|
-
@
|
129
|
-
@
|
148
|
+
compile_exp3 # Call the method for a rule
|
149
|
+
if @match
|
150
|
+
@out.print 'report_error() unless @match' # Print literal string on output stream
|
151
|
+
@out.print "\n" # Print newline on output stream
|
130
152
|
end
|
131
|
-
break if @
|
132
|
-
compile_out
|
133
|
-
if @
|
153
|
+
break if @match
|
154
|
+
compile_out # Call the method for a rule
|
155
|
+
if @match
|
134
156
|
end
|
135
|
-
end while false
|
136
|
-
end while @
|
137
|
-
@
|
138
|
-
|
139
|
-
@
|
140
|
-
@
|
157
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
158
|
+
end while @match # Loop while there is a match
|
159
|
+
@match = true # Since also zero matches is ok set flag here.
|
160
|
+
report_error() unless @match
|
161
|
+
@out.print 'end' # Print literal string on output stream
|
162
|
+
@out.print "\n" # Print newline on output stream
|
141
163
|
end
|
142
|
-
end while false
|
164
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
143
165
|
end
|
144
166
|
def compile_exp1
|
145
167
|
begin
|
146
|
-
@
|
147
|
-
@
|
168
|
+
@out.print 'begin' # Print literal string on output stream
|
169
|
+
@out.print "\n" # Print newline on output stream
|
148
170
|
if true
|
149
|
-
compile_exp2
|
150
|
-
|
171
|
+
compile_exp2 # Call the method for a rule
|
172
|
+
report_error() unless @match
|
151
173
|
begin
|
152
174
|
begin
|
153
|
-
|
154
|
-
|
155
|
-
if @
|
156
|
-
@
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
end while
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@
|
166
|
-
|
167
|
-
end
|
168
|
-
end while false
|
175
|
+
scan '|'
|
176
|
+
if @match
|
177
|
+
@out.print 'break if @match' # Print literal string on output stream
|
178
|
+
@out.print "\n" # Print newline on output stream
|
179
|
+
compile_exp2 # Call the method for a rule
|
180
|
+
report_error() unless @match
|
181
|
+
end
|
182
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
183
|
+
end while @match # Loop while there is a match
|
184
|
+
@match = true # Since also zero matches is ok set flag here.
|
185
|
+
report_error() unless @match
|
186
|
+
@out.print 'end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise' # Print literal string on output stream
|
187
|
+
@out.print "\n" # Print newline on output stream
|
188
|
+
end
|
189
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
169
190
|
end
|
170
191
|
def compile_rule
|
171
192
|
begin
|
172
|
-
|
173
|
-
if @
|
174
|
-
@
|
175
|
-
@
|
176
|
-
@
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
@
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
end
|
188
|
-
end while false
|
193
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
194
|
+
if @match
|
195
|
+
@out.print 'def compile_' # Print literal string on output stream
|
196
|
+
@out.print @match # Print last matched token on output stream
|
197
|
+
@out.print "\n" # Print newline on output stream
|
198
|
+
scan '='
|
199
|
+
report_error() unless @match
|
200
|
+
compile_exp1 # Call the method for a rule
|
201
|
+
report_error() unless @match
|
202
|
+
scan ';'
|
203
|
+
report_error() unless @match
|
204
|
+
@out.print 'end' # Print literal string on output stream
|
205
|
+
@out.print "\n" # Print newline on output stream
|
206
|
+
end
|
207
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
189
208
|
end
|
190
209
|
def compile_program
|
191
210
|
begin
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
@
|
198
|
-
@
|
199
|
-
@
|
200
|
-
@
|
201
|
-
@
|
202
|
-
@
|
203
|
-
@
|
204
|
-
@
|
205
|
-
@
|
206
|
-
@
|
207
|
-
@
|
208
|
-
@
|
209
|
-
@
|
210
|
-
@
|
211
|
-
@
|
212
|
-
@
|
213
|
-
@
|
214
|
-
@
|
211
|
+
scan '.syntax'
|
212
|
+
if @match
|
213
|
+
scan /[A-Za-z]+[A-Za-z0-9_]+/
|
214
|
+
report_error() unless @match
|
215
|
+
@out.print '#!/usr/bin/env ruby' # Print literal string on output stream
|
216
|
+
@out.print "\n" # Print newline on output stream
|
217
|
+
@out.print 'require "strscan"' # Print literal string on output stream
|
218
|
+
@out.print "\n" # Print newline on output stream
|
219
|
+
@out.print 'class StringScanner' # Print literal string on output stream
|
220
|
+
@out.print "\n" # Print newline on output stream
|
221
|
+
@out.print ' # Add scan for string since base StringScanner lack that...' # Print literal string on output stream
|
222
|
+
@out.print "\n" # Print newline on output stream
|
223
|
+
@out.print ' def scan_str(string)' # Print literal string on output stream
|
224
|
+
@out.print "\n" # Print newline on output stream
|
225
|
+
@out.print ' return nil unless self.peek(string.length) == string' # Print literal string on output stream
|
226
|
+
@out.print "\n" # Print newline on output stream
|
227
|
+
@out.print ' self.pos += string.length # Advance the position in the input string' # Print literal string on output stream
|
228
|
+
@out.print "\n" # Print newline on output stream
|
229
|
+
@out.print ' return string' # Print literal string on output stream
|
230
|
+
@out.print "\n" # Print newline on output stream
|
231
|
+
@out.print ' end' # Print literal string on output stream
|
232
|
+
@out.print "\n" # Print newline on output stream
|
233
|
+
@out.print ' alias :old_scan :scan' # Print literal string on output stream
|
234
|
+
@out.print "\n" # Print newline on output stream
|
235
|
+
@out.print ' def scan(strOrRegexp)' # Print literal string on output stream
|
236
|
+
@out.print "\n" # Print newline on output stream
|
237
|
+
@out.print ' String === strOrRegexp ? scan_str(strOrRegexp) : old_scan(strOrRegexp)' # Print literal string on output stream
|
238
|
+
@out.print "\n" # Print newline on output stream
|
239
|
+
@out.print ' end' # Print literal string on output stream
|
240
|
+
@out.print "\n" # Print newline on output stream
|
241
|
+
@out.print 'end' # Print literal string on output stream
|
242
|
+
@out.print "\n" # Print newline on output stream
|
243
|
+
@out.print 'class MetaCompiler_' # Print literal string on output stream
|
244
|
+
@out.print @match # Print last matched token on output stream
|
245
|
+
@out.print "\n" # Print newline on output stream
|
246
|
+
@out.print ' $compiler_class = self # Save class in global var for later reference below' # Print literal string on output stream
|
247
|
+
@out.print "\n" # Print newline on output stream
|
248
|
+
@out.print ' def compile_string(string, outFile)' # Print literal string on output stream
|
249
|
+
@out.print "\n" # Print newline on output stream
|
250
|
+
@out.print ' @in, @out = StringScanner.new(string), outFile' # Print literal string on output stream
|
251
|
+
@out.print "\n" # Print newline on output stream
|
252
|
+
@out.print ' compile_' # Print literal string on output stream
|
253
|
+
@out.print @match # Print last matched token on output stream
|
254
|
+
@out.print ' # call the main compile method to start compiling' # Print literal string on output stream
|
255
|
+
@out.print "\n" # Print newline on output stream
|
256
|
+
@out.print ' end' # Print literal string on output stream
|
257
|
+
@out.print "\n" # Print newline on output stream
|
258
|
+
@out.print ' # Scan for a string or regexp and update state based on match. Skips leading whitespace.' # Print literal string on output stream
|
259
|
+
@out.print "\n" # Print newline on output stream
|
260
|
+
@out.print ' def scan strOrRegexp' # Print literal string on output stream
|
261
|
+
@out.print "\n" # Print newline on output stream
|
262
|
+
@out.print ' @in.scan /\s*/ # Skip whitespace' # Print literal string on output stream
|
263
|
+
@out.print "\n" # Print newline on output stream
|
264
|
+
@out.print ' @match = @in.scan strOrRegexp' # Print literal string on output stream
|
265
|
+
@out.print "\n" # Print newline on output stream
|
266
|
+
@out.print ' # Since nil is same as false in Ruby we can set the flag to the matched token' # Print literal string on output stream
|
267
|
+
@out.print "\n" # Print newline on output stream
|
268
|
+
@out.print ' @last_matched_token = @match if @match # Update last matched only if a token was matched' # Print literal string on output stream
|
269
|
+
@out.print "\n" # Print newline on output stream
|
270
|
+
@out.print ' end' # Print literal string on output stream
|
271
|
+
@out.print "\n" # Print newline on output stream
|
272
|
+
@out.print ' def report_error' # Print literal string on output stream
|
273
|
+
@out.print "\n" # Print newline on output stream
|
274
|
+
@out.print ' pre_lines = @in.string[0, @in.pos].split("\n") # lines of input up to current position' # Print literal string on output stream
|
275
|
+
@out.print "\n" # Print newline on output stream
|
276
|
+
@out.print ' post_lines = @in.rest.split("\n") # lines of input after current position' # Print literal string on output stream
|
277
|
+
@out.print "\n" # Print newline on output stream
|
278
|
+
@out.print ' message = "PARSE ERROR at line #{pre_lines.length}:\n " + pre_lines.last.inspect + " @ "' # Print literal string on output stream
|
279
|
+
@out.print "\n" # Print newline on output stream
|
280
|
+
@out.print ' message += post_lines.first.inspect' # Print literal string on output stream
|
281
|
+
@out.print "\n" # Print newline on output stream
|
282
|
+
@out.print ' message += "\n Last matched token: #{@last_matched_token}"' # Print literal string on output stream
|
283
|
+
@out.print "\n" # Print newline on output stream
|
284
|
+
@out.print ' raise message' # Print literal string on output stream
|
285
|
+
@out.print "\n" # Print newline on output stream
|
286
|
+
@out.print ' end' # Print literal string on output stream
|
287
|
+
@out.print "\n" # Print newline on output stream
|
288
|
+
@out.print ' def self.compile_file(inFile, out = nil)' # Print literal string on output stream
|
289
|
+
@out.print "\n" # Print newline on output stream
|
290
|
+
@out.print ' outfh = (out == nil ? STDOUT : File.open(out, "w"))' # Print literal string on output stream
|
291
|
+
@out.print "\n" # Print newline on output stream
|
292
|
+
@out.print ' self.new.compile_string(File.read(inFile), outfh)' # Print literal string on output stream
|
293
|
+
@out.print "\n" # Print newline on output stream
|
294
|
+
@out.print ' outfh.close if out == nil' # Print literal string on output stream
|
295
|
+
@out.print "\n" # Print newline on output stream
|
296
|
+
@out.print ' end' # Print literal string on output stream
|
297
|
+
@out.print "\n" # Print newline on output stream
|
215
298
|
begin
|
216
|
-
compile_rule
|
217
|
-
end while @
|
218
|
-
@
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
@
|
224
|
-
@
|
225
|
-
@
|
226
|
-
@
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
299
|
+
compile_rule # Call the method for a rule
|
300
|
+
end while @match # Loop while there is a match
|
301
|
+
@match = true # Since also zero matches is ok set flag here.
|
302
|
+
report_error() unless @match
|
303
|
+
scan '.end'
|
304
|
+
report_error() unless @match
|
305
|
+
@out.print 'end' # Print literal string on output stream
|
306
|
+
@out.print "\n" # Print newline on output stream
|
307
|
+
@out.print 'if ARGV.length < 1 || ARGV.length > 2' # Print literal string on output stream
|
308
|
+
@out.print "\n" # Print newline on output stream
|
309
|
+
@out.print ' puts "ERROR: wrong number of parameters\n\n"' # Print literal string on output stream
|
310
|
+
@out.print "\n" # Print newline on output stream
|
311
|
+
@out.print ' puts "Usage: #{File.basename($0)} <input_file> [output_file]"' # Print literal string on output stream
|
312
|
+
@out.print "\n" # Print newline on output stream
|
313
|
+
@out.print ' exit(-1)' # Print literal string on output stream
|
314
|
+
@out.print "\n" # Print newline on output stream
|
315
|
+
@out.print 'else' # Print literal string on output stream
|
316
|
+
@out.print "\n" # Print newline on output stream
|
317
|
+
@out.print ' $compiler_class.compile_file(ARGV[0], ARGV[1])' # Print literal string on output stream
|
318
|
+
@out.print "\n" # Print newline on output stream
|
319
|
+
@out.print 'end' # Print literal string on output stream
|
320
|
+
@out.print "\n" # Print newline on output stream
|
321
|
+
end
|
322
|
+
end while false # This "while false" is needed since the break is not parsed correctly by Ruby otherwise
|
323
|
+
end
|
324
|
+
end
|
325
|
+
if ARGV.length < 1 || ARGV.length > 2
|
326
|
+
puts "ERROR: wrong number of parameters\n\n"
|
327
|
+
puts "Usage: #{File.basename($0)} <input_file> [output_file]"
|
328
|
+
exit(-1)
|
329
|
+
else
|
330
|
+
$compiler_class.compile_file(ARGV[0], ARGV[1])
|
331
|
+
end
|