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 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/meta_compile) is 231 lines of Ruby code.
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
- + Very bad/little error handling
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 bootstrap_re
65
+ rake boot_regexp
66
66
 
67
67
  Then we create a compiler for the assignments syntax:
68
68
 
69
- ruby bin/metacomp_re syntaxes/assignments.meta > tas.rb
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 :bootstrap_c do
13
- Dir.chdir "bootstrap"
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_for_c.txt description of itself"
17
- pexec "./bootstrapped_c meta_for_c.txt 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_for_c.txt 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"
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
- desc "Bootstrap the Ruby Meta-II compiler"
32
- task :bootstrap do
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 ../syntaxes/meta_to_ruby_minimal.meta compile_to_ruby.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 ../syntaxes/meta_to_ruby_minimal.meta meta_ruby_compiler_from_c.rb"
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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler.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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler2.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 ../syntaxes/meta_to_ruby_minimal.meta > meta_ruby_compiler3.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('../syntaxes/meta_to_ruby_minimal.meta')} line meta-II spec\n\n"
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 bootstrap_from_meta_compile(syntaxFile)
58
- pexec "meta_compile #{syntaxFile} > tgen.rb"
59
- ensure_is_meta("tgen.rb", syntaxFile)
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
- desc "Make binary from the bootstrapped ruby meta-II compiler"
84
- task :make_bin => [:bootstrap] do
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 :bootstrap_re do
105
- bootstrap_with_stepping_stone("meta_to_ruby_minimal_with_regexps.meta", "bin/metacomp_re")
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 metacomp_re_run_examples(syntaxFile, *inputs)
116
- pexec "ruby bin/metacomp_re #{syntaxFile} > tas.rb"
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
- metacomp_re_run_examples "syntaxes/assignments.meta", "assignments.input1"
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 => [:bootstrap] do
129
- rmeta2_compiler_loc = loc('bootstrap/meta_ruby_compiler.rb')
130
- rmeta2_spec_loc = loc('syntaxes/meta_to_ruby_minimal.meta')
131
- readme = File.read("README.template.md").gsub("%%RMetaII_SPEC_LOC%%", rmeta2_spec_loc.to_s)
132
- readme = readme.gsub("%%RMetaII_COMPILER_LOC%%", rmeta2_compiler_loc.to_s)
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 => [:make_bin] do
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 => [:update, :install_gem] do
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
@@ -1,231 +1,331 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "strscan"
3
- class C_program
4
- $c = self
5
- def compile(str, out)
6
- @i, @o = StringScanner.new(str), out
7
- compile_program
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
- @i.scan /\s*/; s='$'; l=s.length;
12
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
13
- if @f
14
- @o.print '@o.print @t'
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 @f
18
- @i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/
19
- if @f
20
- @o.print '@o.print '
21
- @o.print @t
22
- @o.print "\n"
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
- @i.scan /\s*/; s='<'; l=s.length;
29
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
30
- if @f
60
+ scan '<'
61
+ if @match
31
62
  begin
32
- compile_outarg
33
- end while @f
34
- @f = true
35
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
36
- @i.scan /\s*/; s='>'; l=s.length;
37
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
38
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
39
- @o.print '@o.print "\n"'
40
- @o.print "\n"
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
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
47
- if @f
48
- @o.print 'compile_'
49
- @o.print @t
50
- @o.print "\n"
51
- end
52
- break if @f
53
- @i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/
54
- if @f
55
- @o.print '@i.scan /\s*/; s='
56
- @o.print @t
57
- @o.print '; l=s.length;'
58
- @o.print "\n"
59
- @o.print '@f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil'
60
- @o.print "\n"
61
- end
62
- break if @f
63
- @i.scan /\s*/; s='.id'; l=s.length;
64
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
65
- if @f
66
- @o.print '@i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/'
67
- @o.print "\n"
68
- end
69
- break if @f
70
- @i.scan /\s*/; s='.string'; l=s.length;
71
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
72
- if @f
73
- @o.print '@i.scan /\s*/; @f = @t = @i.scan /\047[^\047]*\047/'
74
- @o.print "\n"
75
- end
76
- break if @f
77
- @i.scan /\s*/; s='('; l=s.length;
78
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
79
- if @f
80
- compile_exp1
81
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
82
- @i.scan /\s*/; s=')'; l=s.length;
83
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
84
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
85
- end
86
- break if @f
87
- @i.scan /\s*/; s='.e'; l=s.length;
88
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
89
- if @f
90
- @o.print '@f = true'
91
- @o.print "\n"
92
- end
93
- break if @f
94
- @i.scan /\s*/; s='*'; l=s.length;
95
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
96
- if @f
97
- @o.print 'begin'
98
- @o.print "\n"
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 @f
113
- @o.print 'if @f'
114
- @o.print "\n"
115
- end
116
- break if @f
117
- compile_out
118
- if @f
119
- @o.print 'if true'
120
- @o.print "\n"
121
- end
122
- end while false
123
- if @f
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 @f
128
- @o.print 'raise("error at: " + @i.rest.split("\n")[0]) if !@f'
129
- @o.print "\n"
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 @f
132
- compile_out
133
- if @f
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 @f
137
- @f = true
138
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
139
- @o.print 'end'
140
- @o.print "\n"
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
- @o.print 'begin'
147
- @o.print "\n"
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
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
171
+ compile_exp2 # Call the method for a rule
172
+ report_error() unless @match
151
173
  begin
152
174
  begin
153
- @i.scan /\s*/; s='|'; l=s.length;
154
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
155
- if @f
156
- @o.print 'break if @f'
157
- @o.print "\n"
158
- compile_exp2
159
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
160
- end
161
- end while false
162
- end while @f
163
- @f = true
164
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
165
- @o.print 'end while false'
166
- @o.print "\n"
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
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
173
- if @f
174
- @o.print 'def compile_'
175
- @o.print @t
176
- @o.print "\n"
177
- @i.scan /\s*/; s='='; l=s.length;
178
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
179
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
180
- compile_exp1
181
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
182
- @i.scan /\s*/; s=';'; l=s.length;
183
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
184
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
185
- @o.print 'end'
186
- @o.print "\n"
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
- @i.scan /\s*/; s='.syntax'; l=s.length;
193
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
194
- if @f
195
- @i.scan /\s*/; @f = @t = @i.scan /[A-Za-z]+[A-Za-z0-9_]+/
196
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
197
- @o.print '#!/usr/bin/env ruby'
198
- @o.print "\n"
199
- @o.print 'require "strscan"'
200
- @o.print "\n"
201
- @o.print 'class C_'
202
- @o.print @t
203
- @o.print "\n"
204
- @o.print '$c = self'
205
- @o.print "\n"
206
- @o.print 'def compile(str, out)'
207
- @o.print "\n"
208
- @o.print '@i, @o = StringScanner.new(str), out'
209
- @o.print "\n"
210
- @o.print 'compile_'
211
- @o.print @t
212
- @o.print "\n"
213
- @o.print 'end'
214
- @o.print "\n"
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 @f
218
- @f = true
219
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
220
- @i.scan /\s*/; s='.end'; l=s.length;
221
- @f = (@i.peek(l) == s) ? (@t=s; @i.pos += l) : nil
222
- raise("error at: " + @i.rest.split("\n")[0]) if !@f
223
- @o.print 'end'
224
- @o.print "\n"
225
- @o.print '$c.new.compile(File.read(ARGV[0]), STDOUT)'
226
- @o.print "\n"
227
- end
228
- end while false
229
- end
230
- end
231
- $c.new.compile(File.read(ARGV[0]), STDOUT)
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