misc fixes to LM
[light-and-matter.git] / fruby
blob046549d4b2a26ed211d572e1377b0f4a67fec135
1 #!/usr/bin/ruby
3 # (c) 2008 B. Crowell, GPL v 2
5 # This is designed to be more or less a drop-in replacement for eruby. It supports a subset
6 # of eruby, and has better error handling.
7 # Usage:
8 # fruby infile >outfile
9 # How it's different:
10 # - Better error handling.
11 # - Parses and evaluates the input file on the fly, rather than
12 # translating it into a .rb file.
13 # - Supports <% %>, <%= %>, and <%# %>, but not % at beginning of line. This makes
14 # it compatible with TeX.
15 # - Doesn't support this kind of thing:
16 # <% 3.times do %> rah <% end %>
17 # <% if foo %> foo <% end %>
18 # Every block has to be a syntactically self-contained piece of ruby code.
19 # Variables assigned to in one piece of code *are* available in later pieces of code.
20 # This type of thing has to be accomplished by using a single block of ruby code,
21 # with print statements inside it.
22 # Bugs:
23 # - Doesn't check if <% and %> are properly matched.
24 # Security:
25 # Will happily run any code in the file.
27 def die(message)
28 $stderr.print "fruby: " + message + "\n"
29 exit(-1)
30 end
32 # The following dummy subroutine is the environment in which we evaluate the code.
33 # see pickaxe book, pp. 296, 419
34 # This accomplishes two things for us:
35 # - If we change a variable in one <% %> block, the change persists in later blocks.
36 # - Local variables in <% %> blocks are separate from local variables in this program.
37 def binding_for_eval()
38 return binding()
39 end
41 if ARGV.empty? then die("Usage: fruby infile >outfile") end
42 infile = ARGV[0]
43 if ! File.exist?(infile) then die("File #{infile} does not exist.") end
44 t = nil
45 File.open(infile,'r') { |f|
46 t = f.gets(nil) # nil means read whole file
49 b = binding_for_eval()
50 inside = false # even if there is a ruby expression at the very beginning of the string, split() gives us a null string as our first string
51 line = 1
52 t.split(/(?:\<\%|\%\>)/).each { |x|
53 if inside then
54 what = 'execute'
55 if x=~/\A=/ then what='evaluate' end
56 if x=~/\A#/ then what='comment' end
57 x.gsub!(/\A[=#]/,'')
58 if what != 'comment' then
59 y = eval(x,b,infile,line)
60 if what == 'evaluate' then print y end
61 end
62 else
63 print x
64 end
65 x.scan(/\n/) {line += 1}
66 inside = !inside