citrus-compiler 0.8.0

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/LICENSE ADDED
@@ -0,0 +1,32 @@
1
+ Copyright (c) 2011 Mac Malone
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions
7
+ are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the author nor the names of his contributors
17
+ may be used to endorse or promote products derived from this software
18
+ without specific prior written permission.
19
+
20
+ This license is subject to change without notice.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
23
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
26
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
+ POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,63 @@
1
+ = Citrus
2
+
3
+ Author:: Mac Malone
4
+ License:: See LICENSE
5
+ Copyright:: Copyright (c) 2010 Mac Malone
6
+
7
+ Citrus is a simple dynamically typed linear language written in Ruby, Treetop, and LLVM. It is meant to be an example of how to write a basic compiler.
8
+
9
+ Being linear, it could be called very c-like, but it has been written to have a syntax closer to Ruby. Its current main features are:
10
+
11
+ * Dynamically typed
12
+ * Extremely ruby-like syntax
13
+ * Integer, Float, String, Boolean, and Array types
14
+ * Easy integration of C functions into the API
15
+
16
+ It's prime examples are in the example directory, but here is an excerpt from factorial.ct:
17
+
18
+ def factorial(num)
19
+ if num == 0
20
+ fac = 1
21
+ else
22
+ fac = num*factorial(num-1)
23
+ end
24
+ return fac
25
+ end
26
+
27
+ printf("factorial(%d): %d", 6, factorial(6))
28
+
29
+ As you can see, very ruby-like.
30
+
31
+ == Installing & Requirements
32
+
33
+ Citrus requires the following libraries:
34
+
35
+ * LLVM 2.8 compiled with shared libraries
36
+ * On Mac OS X, the quickest way is to use `brew install llvm --shared`
37
+ * On Linux, there are usually pre-compiled packages
38
+ * Ruby
39
+ * Treetop
40
+ * Ruby LLVM 2.9.1
41
+
42
+ Everything except LLVM is installed when running `gem install citrus`
43
+
44
+ == Running
45
+
46
+ Citrus is a pretty simple thing to run:
47
+
48
+ $ ruby bin/citrus [options] FILENAME
49
+ -O Doesn't run optimization
50
+ -i Dumps bitcode
51
+ -c NAME Compiles to the given file name
52
+ -h Prints help
53
+
54
+ If citrus is run without -c it JIT complies the code, running it in place.
55
+
56
+ == Problems & Todos
57
+
58
+ * Hashs
59
+ * Create a standard library
60
+ * Better dynamically typed arrays (using structures)
61
+ * Better exception handling (currently only works semi-effectively within begin blocks)
62
+ * Garbage Collection
63
+
data/bin/citrus.rb ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby -s
2
+ $:.unshift File.dirname(__FILE__) + "/../lib"
3
+ require "citrus"
4
+
5
+ if $h
6
+ puts <<-EOS
7
+
8
+ ~= Citrus =~
9
+
10
+ To run a file
11
+ citrus fruit.ct
12
+
13
+ To compile to native application
14
+ citrus -c=lemon lime.ct
15
+
16
+ EOS
17
+ exit
18
+ end
19
+
20
+ file = ARGV.first
21
+ abort "Usage: citrus [-hiO] [-c=file.o] file.or" unless file
22
+
23
+ g = Citrus.compile_file(File.expand_path(file))
24
+ g.optimize unless $O
25
+
26
+ case
27
+ when $i: g.inspect
28
+ when $c: g.compile(TrueClass === $c ? File.basename(file, File.extname(file)) : $c)
29
+ else g.run
30
+ end
@@ -0,0 +1,14 @@
1
+ # Citrus factorials...
2
+
3
+ def factorial(num)
4
+ if num == 0
5
+ fac = 1
6
+ else
7
+ fac = num*factorial(num-1)
8
+ end
9
+ return fac
10
+ end
11
+
12
+ puts("== factorial ")
13
+ printf("factorial(%d): %d", 6, factorial(6))
14
+ puts("")
data/example/simple.ct ADDED
@@ -0,0 +1,69 @@
1
+ =begin
2
+ A simple test...
3
+ Included in Citrus source code
4
+ v1.0
5
+ =end
6
+
7
+ require 'factorial.ct'
8
+
9
+ $exitcode = 7
10
+
11
+ def input
12
+ buf = " "
13
+ read(0, buf, 1)
14
+ puts(buf)
15
+ end
16
+
17
+ def compare
18
+ num = 7
19
+ cond = (num == 4.5 + 2.5)
20
+ if cond
21
+ puts("7 = 4.5 + 2.5")
22
+ end
23
+ if num == 6 - 3
24
+ puts("6 - 3 = 7")
25
+ else
26
+ puts("6 - 3 != 7")
27
+ end
28
+ end
29
+
30
+ def iterate
31
+ iteration = 10
32
+ while iteration >= 0
33
+ printf("%d ", iteration)
34
+ iteration = iteration - 1
35
+ end
36
+ puts("")
37
+ array = ["lift", "off", "and", "away"]
38
+ for iteration in array
39
+ printf("%s ", iteration)
40
+ end
41
+ puts("")
42
+ end
43
+
44
+ def handle(err)
45
+ begin
46
+ puts("beginning exception handling...")
47
+ if err
48
+ raise(" - Erred! - ")
49
+ end
50
+ rescue
51
+ puts($!)
52
+ else
53
+ puts(" - No Error - ")
54
+ ensure
55
+ puts("...end of exception handling")
56
+ end
57
+ end
58
+
59
+ puts("== input ")
60
+ input
61
+ puts("== compare ")
62
+ compare
63
+ puts("== iterate ")
64
+ iterate
65
+ puts("== handle ")
66
+ handle(true)
67
+ handle(false)
68
+ puts("== exit ")
69
+ exit($exitcode)
data/lib/citrus.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "rubygems"
2
+ require "treetop"
3
+
4
+ require "citrus/core"
5
+ require "citrus/runtime"
6
+ require "citrus/compiler"
7
+ require "citrus/exceptions"
8
+ require "citrus/nodes"
9
+
10
+ if File.file?(File.dirname(__FILE__) + "/citrus/grammar.rb")
11
+ # Take compiled one
12
+ require "citrus/grammar"
13
+ else
14
+ Treetop.load File.dirname(__FILE__) + "/citrus/grammar.tt"
15
+ end
16
+
17
+ module Citrus
18
+
19
+ def self.compile(code)
20
+ $compiler = Citrus::Compiler.new
21
+ $parser = CitrusParser.new
22
+
23
+ if node = $parser.parse(code)
24
+ node.compile($compiler)
25
+ else
26
+ error(SyntaxError.new)
27
+ end
28
+
29
+ $compiler
30
+ end
31
+
32
+ def self.compile_file(file)
33
+ $file = file
34
+ self.compile(File.read(file))
35
+ end
36
+
37
+ end
@@ -0,0 +1,65 @@
1
+ require "citrus/compiler/generator"
2
+ require "citrus/compiler/block"
3
+ require "citrus/compiler/function"
4
+ require "citrus/compiler/variable"
5
+ require "citrus/compiler/global_strings"
6
+
7
+ require "tempfile"
8
+
9
+ module Citrus
10
+ class Compiler
11
+
12
+ attr_reader :generator
13
+ attr_reader :module
14
+
15
+ def initialize
16
+ @module = LLVM::Module.create("Citrus")
17
+ GlobalVariables.init(@module)
18
+ GlobalFunctions.init(@module)
19
+ @function = @module.functions.add("main", LLVM::Type.function([INT, LLVM::Type.pointer(PCHAR)], INT))
20
+ @generator = Generator.new(@module, @function)
21
+ GlobalStrings.init(@generator.builder)
22
+ end
23
+
24
+ def preamble
25
+ Runtime.build_lib(@generator)
26
+ end
27
+
28
+ def finish
29
+ @generator.builder.ret(INT.from_i(0))
30
+ @generator.finish
31
+ end
32
+
33
+ def run
34
+ @engine = LLVM::ExecutionEngine.create_jit_compiler(@module)
35
+ @engine.run_function(@function, 0, 0)
36
+ end
37
+
38
+ def to_file(file)
39
+ @module.write_bitcode(file)
40
+ end
41
+
42
+ def compile(file)
43
+ #bc = Tempfile.new("#{file}.bc", "/tmp")
44
+ #as = Tempfile.new("#{file}.s", "/tmp")
45
+ to_file("#{file}.ctc.bc")
46
+ tool = LLVM.bin_path.empty? ? "llc" : File.join(LLVM.bin_path, "llc")
47
+ %x[#{tool} #{file}.ctc.bc -o #{file}.ctc.s]
48
+ %x[gcc #{file}.ctc.s -o #{file}]
49
+ File.delete("#{file}.ctc.bc")
50
+ File.delete("#{file}.ctc.s")
51
+ #bc.close!
52
+ #as.close!
53
+ end
54
+
55
+ def optimize
56
+ PassManager.new(@engine).run(@module) unless @engine.nil?
57
+ end
58
+
59
+ def inspect
60
+ @module.dump
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -0,0 +1,29 @@
1
+ module Citrus
2
+ class Block
3
+
4
+ def initialize(mod, function, parent)
5
+ @module = mod
6
+ @function = function
7
+ @parent = parent
8
+ build_block { |g| yield g if block_given? }
9
+ end
10
+
11
+ def basic_block
12
+ return @generator.basic_block
13
+ end
14
+ alias :bb :basic_block
15
+
16
+ def locals
17
+ return @generator.locals
18
+ end
19
+
20
+ private
21
+
22
+ def build_block
23
+ @generator = Generator.new(@module, @function, @parent)
24
+ yield @generator if block_given?
25
+ @generator.finish
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ module Citrus
2
+ class Function
3
+
4
+ attr_reader :varargs
5
+ attr_reader :generator
6
+
7
+ def initialize(name, args, mod)
8
+ @name = name
9
+ @module = mod
10
+ @rtype = LLVM::Type.opaque
11
+ @atypes = args.map{LLVM::Type.opaque}
12
+ @args = args.map{|arg| arg.class.to_s == "String" ? arg.to_a : arg}
13
+ @varargs = @args.empty? ? false : (@args.last[0].slice(0, 1) == "*")
14
+ @func = @module.functions.add(name, LLVM::Type.function(@atypes, @rtype, :varargs => @vaargs))
15
+ build_function { |g| yield g if block_given? }
16
+ end
17
+
18
+ def args
19
+ return @args.map{|arg| arg[0]}
20
+ end
21
+
22
+ def return(value, builder)
23
+ @rtype.refine(LLVM::Type(value)) if @rtype.kind == :opaque
24
+ builder.ret(value)
25
+ end
26
+
27
+ def call(args, builder)
28
+ for i in 0...@atypes.size
29
+ @atypes[i].refine(LLVM::Type(args[i])) if @atypes[i].kind == :opaque
30
+ end
31
+ builder.call(@func, *args)
32
+ end
33
+
34
+ def force_types(args, ret=nil)
35
+ unless args.empty?
36
+ for i in 0...@atypes.size
37
+ @atypes[i].refine(LLVM::Type(args[i])) if @atypes[i].kind == :opaque
38
+ end
39
+ end
40
+ unless ret.nil?
41
+ @rtype.refine(ret) if @rtype.kind == :opaque
42
+ end
43
+ end
44
+
45
+ def method_missing(symbol, *args, &block)
46
+ @func.send(symbol, *args, &block)
47
+ end
48
+
49
+ private
50
+
51
+ def build_function
52
+ @generator = Generator.new(@module, self)
53
+ @args.each do |arg|
54
+ @generator.assign(arg[0], @func.params[@args.index(arg)])
55
+ end
56
+ yield generator
57
+ @generator.return
58
+ @generator.finish
59
+ end
60
+
61
+ end
62
+
63
+ class GlobalFunctions
64
+
65
+ def self.init(mod)
66
+ @module ||= mod
67
+ @functions ||= {}
68
+ end
69
+
70
+ def self.add(name, args)
71
+ return @functions[name] = Function.new(name, args, @module) { |g| yield g if block_given? }
72
+ end
73
+
74
+ def self.named(name)
75
+ return @functions[name]
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,303 @@
1
+ module Citrus
2
+ class Generator
3
+
4
+ attr_reader :basic_block
5
+ attr_reader :module
6
+ attr_reader :builder
7
+ attr_reader :locals
8
+ attr_reader :parent
9
+
10
+ def initialize(mod, function, parent=nil)
11
+ @module = mod
12
+ @locals = {}
13
+ @parent = parent
14
+ @function = function
15
+ @basic_block = @function.basic_blocks.append
16
+ @builder = LLVM::Builder.create
17
+ @builder.position_at_end(@basic_block)
18
+ end
19
+
20
+ def array(values)
21
+ ary = @builder.alloca(LLVM::Array(LLVM::Type(values.first), values.size))
22
+ for index in 0...values.size
23
+ ptr = @builder.gep(ary, [INT.from_i(0), INT.from_i(index)])
24
+ @builder.store(values[index], ptr)
25
+ end
26
+ return ary
27
+ end
28
+
29
+ def string(value)
30
+ ptr = GlobalStrings.pointer(value)
31
+ end
32
+
33
+ def float(value)
34
+ FLOAT.from_f(value)
35
+ end
36
+
37
+ def number(value)
38
+ INT.from_i(value)
39
+ end
40
+
41
+ def bool(value)
42
+ BOOL.from_i(value ? 1 : 0)
43
+ end
44
+
45
+ def not(value)
46
+ @builder.not(value)
47
+ end
48
+
49
+ def call(func, *args)
50
+ begin
51
+ GlobalFunctions.named(func).call(args, @builder)
52
+ rescue NoMethodError # for C methods
53
+ f = @module.functions.named(func)
54
+ raise NoMethodError if f.nil?
55
+ @builder.call(f, *args)
56
+ end
57
+ end
58
+
59
+ def resolve_conflicts(*gens)
60
+ locals = []
61
+ gens.each{|g| locals += g.locals.keys}
62
+ stat = locals.inject(Hash.new(0)){|h, e| h[e]+=1; h}
63
+ stat.select{|k, v| v > 1}.collect{|a| a[0]}.each do |k|
64
+ resolve_conflict(k, *gens)
65
+ end
66
+ end
67
+
68
+ def resolve_conflict(name, *gens)
69
+ ty = INT
70
+ nodes = {}
71
+ gens.select{|g| g.locals.has_key?(name)}.each do |g|
72
+ bb = g.basic_block
73
+ var = g.locals[name]
74
+ builder = LLVM::Builder.create
75
+ if bb == @builder.insert_block && !nodes.has_key?(bb.previous)
76
+ builder.position_before(bb.previous.instructions.last)
77
+ nodes[bb.previous] = var.value(builder)
78
+ else
79
+ builder.position_before(bb.instructions.last)
80
+ nodes[bb] = var.value(builder)
81
+ end
82
+ builder.dispose
83
+ end
84
+ ptr = @builder.phi(ty, nodes)
85
+ self.assign(name, ptr)
86
+ end
87
+
88
+ def assign(name, value)
89
+ unless @locals.has_key?(name)
90
+ @locals[name] = Variable.new(value, @builder)
91
+ else
92
+ @locals[name].assign(value, @builder)
93
+ end
94
+ end
95
+
96
+ def assign_global(name, value)
97
+ unless GlobalVariables.named(name)
98
+ GlobalVariables.add(name, value, @builder)
99
+ else
100
+ GlobalVariables.named(name).assign(value, @builder)
101
+ end
102
+ end
103
+
104
+ def assign_index(name, index, value)
105
+ ary = @locals[name].value(@builder)
106
+ ptr = @builder.gep(ary, [INT.from_i(0), index])
107
+ @builder.store(value, ptr)
108
+ end
109
+
110
+ def compare(op, lval, rval)
111
+ case op.to_s
112
+ when *CMP_MAPPING.keys
113
+ if LLVM::Type(lval) == FLOAT.type || LLVM::Type(rval) == FLOAT.type
114
+ symbol = "o#{CMP_MAPPING[op.to_s].to_s}".to_sym
115
+ if LLVM::Type(lval) != FLOAT.type
116
+ lval = @builder.ui2fp(lval, FLOAT.type)
117
+ elsif LLVM::Type(rval) != FLOAT.type
118
+ rval = @builder.ui2fp(rval, FLOAT.type)
119
+ end
120
+ @builder.fcmp(symbol, lval, rval)
121
+ else
122
+ symbol = CMP_MAPPING[op.to_s]
123
+ symbol = "s#{symbol.to_s}".to_sym unless symbol == :eq || symbol == :ne
124
+ @builder.icmp(symbol, lval, rval)
125
+ end
126
+ when "&&", "and"
127
+ @builder.and(lval, rval)
128
+ when "||", "or"
129
+ @builder.or(lval, rval)
130
+ end
131
+ end
132
+
133
+ def equate(op, lval, rval)
134
+ if LLVM::Type(lval) == FLOAT.type || LLVM::Type(rval) == FLOAT.type
135
+ symbol = "f#{EQU_MAPPING[op.to_s].to_s}".to_sym
136
+ if LLVM::Type(lval) != FLOAT.type
137
+ lval = @builder.ui2fp(lval, FLOAT.type)
138
+ elsif LLVM::Type(rval) != FLOAT.type
139
+ rval = @builder.ui2fp(rval, FLOAT.type)
140
+ end
141
+ @builder.send(symbol, lval, rval)
142
+ else
143
+ symbol = EQU_MAPPING[op.to_s]
144
+ symbol = :sdiv if symbol == :div
145
+ @builder.send(symbol, lval, rval)
146
+ end
147
+ end
148
+
149
+ def vars
150
+ return @parent.nil? ? @locals : @locals.merge(@parent.vars)
151
+ end
152
+
153
+ def load(name)
154
+ if @locals.has_key?(name)
155
+ @locals[name].value(@builder)
156
+ else
157
+ self.vars[name].value(@builder)
158
+ end
159
+ end
160
+
161
+ def load_global(name)
162
+ GlobalVariables.named(name).value(@builder)
163
+ end
164
+
165
+ def load_index(ary, index)
166
+ @builder.load(@builder.gep(ary, [INT.from_i(0), index]))
167
+ end
168
+
169
+ def function(name, args)
170
+ GlobalFunctions.add(name, args) { |g| yield g }
171
+ end
172
+
173
+ def declare(name, args, ret, varargs = false)
174
+ rtype = DEC_MAPPING[ret.to_sym]
175
+ atypes = args.map{|arg| DEC_MAPPING[arg.to_sym]}
176
+ @module.functions.add(name.to_s, LLVM::Type.function(atypes, rtype, :varargs => varargs))
177
+ end
178
+
179
+ def block
180
+ Block.new(@module, @function, self) { |g| yield g if block_given? }
181
+ end
182
+
183
+ def condition(cond, thenblock, elseblock, elsifs=[])
184
+ @basic_block = self.block.bb
185
+ eb = elsifs.empty? ? elseblock : self.block
186
+ efbs = []
187
+ @builder.cond(cond, thenblock.bb, eb.bb)
188
+ for i in 0...elsifs.length
189
+ efbs += eb
190
+ @builder.position_at_end(eb.bb)
191
+ neb = i+1 == elsifs.length ? elseblock : self.block
192
+ @builder.cond(elsifs[i][0], elsifs[i][1].bb, eb.bb)
193
+ @builder.position_at_end(elsifs[i][1].bb)
194
+ @builder.br(@basic_block)
195
+ eb = neb
196
+ end
197
+ @builder.position_at_end(thenblock.bb)
198
+ @builder.br(@basic_block)
199
+ @builder.position_at_end(elseblock.bb)
200
+ @builder.br(@basic_block)
201
+ @builder.position_at_end(@basic_block)
202
+ self.resolve_conflicts(thenblock, elseblock, *efbs)
203
+ end
204
+
205
+ def case(val, cases, elseblock)
206
+ ncases = {}
207
+ for pair in cases
208
+ ncases[pair[0].bb] = pair[1]
209
+ end
210
+ switch = @builder.switch(val, elseblock.bb, ncases)
211
+ @basic_block = self.block.bb
212
+ @builder.position_at_end(elseblock.bb)
213
+ @builder.br(@basic_block)
214
+ ncases.each_value do |c|
215
+ @builder.position_at_end(c[1])
216
+ @builder.br(@basic_block)
217
+ end
218
+ @builder.position_at_end(@basic_block)
219
+ self.resolve_conflicts(elseblock, *cases.keys)
220
+ end
221
+
222
+ def begin(rblock, elblock, enblock)
223
+ cond = self.compare(:==, self.load_global(STS_GLOBAL), self.number(1))
224
+ @builder.cond(cond, rblock.bb, elblock.bb)
225
+ @basic_block = self.block.bb
226
+ @builder.position_before(rblock.bb.instructions.first)
227
+ self.assign_global(STS_GLOBAL, self.number(0))
228
+ @builder.position_at_end(rblock.bb)
229
+ @builder.br(enblock.bb)
230
+ @builder.position_at_end(elblock.bb)
231
+ @builder.br(enblock.bb)
232
+ @builder.position_before(enblock.bb.instructions.first)
233
+ self.resolve_conflicts(rblock, elblock)
234
+ @builder.position_at_end(enblock.bb)
235
+ @builder.br(@basic_block)
236
+ @builder.position_at_end(@basic_block)
237
+ @locals.merge!(enblock.locals)
238
+ end
239
+
240
+ def unwind
241
+ @builder.unwind
242
+ end
243
+
244
+ # Needs to be called before running any loop command
245
+ # (specifically before calculating the conditions for the loop)
246
+ def preploop(looptype=nil, *args)
247
+ if looptype == :for
248
+ self.assign("for", INT.from_i(0))
249
+ self.assign(args[0], self.load_index(args[1], self.number(0)))
250
+ end
251
+ @basic_block = self.block.bb
252
+ @builder.br(@basic_block)
253
+ @builder.position_at_end(@basic_block)
254
+ end
255
+
256
+ def while(cond)
257
+ @builder.position_before(@basic_block.instructions.first)
258
+ generator = Generator.new(@module, @function, self)
259
+ yield generator
260
+ generator.break(@basic_block)
261
+ generator.finish
262
+ self.resolve_conflicts(generator, self)
263
+ @builder.position_at_end(@basic_block)
264
+ @basic_block = self.block.bb
265
+ @builder.cond(cond, generator.basic_block, @basic_block)
266
+ @builder.position_at_end(@basic_block)
267
+ end
268
+
269
+ def for(var, indices)
270
+ generator = Generator.new(@module, @function, self)
271
+ generator.assign(var, generator.load_index(indices, generator.load("for")))
272
+ yield generator
273
+ generator.assign("for", generator.equate(:+, generator.load("for"), generator.number(1)))
274
+ generator.break(@basic_block)
275
+ generator.finish
276
+ @builder.position_at_end(@basic_block)
277
+ self.resolve_conflict("for", generator, self)
278
+ @basic_block = self.block.bb
279
+ size = LLVM::C.LLVMGetArrayLength(LLVM::Type(indices).element_type)
280
+ cond = self.compare(:<, self.load("for"), self.number(size))
281
+ @builder.cond(cond, generator.basic_block, @basic_block)
282
+ @builder.position_at_end(@basic_block)
283
+ end
284
+
285
+ def return(value=self.number(0))
286
+ unless @finished
287
+ @function.return(value, @builder)
288
+ @finished = true
289
+ end
290
+ end
291
+
292
+ def break(block)
293
+ unless @finished
294
+ @builder.br(block)
295
+ end
296
+ end
297
+
298
+ def finish
299
+ @builder.dispose
300
+ end
301
+
302
+ end
303
+ end
@@ -0,0 +1,19 @@
1
+ module Citrus
2
+ class GlobalStrings
3
+
4
+ def self.init(builder)
5
+ @builder ||= builder
6
+ @pointers ||= {}
7
+ end
8
+
9
+ def self.pointer(value)
10
+ if @pointers.has_key?(value)
11
+ return @pointers[value]
12
+ else
13
+ @pointers[value] = @builder.global_string_pointer(value)
14
+ return @pointers[value]
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ module Citrus
2
+ class Variable
3
+
4
+ attr_reader :type
5
+ attr_reader :pointer
6
+
7
+ def initialize(value, builder)
8
+ @type = LLVM::Type(value)
9
+ @pointer = builder.alloca(@type)
10
+ builder.store(value, @pointer)
11
+ end
12
+
13
+ def assign(value, builder)
14
+ type = LLVM::Type(value)
15
+ unless type == @type
16
+ @type = type
17
+ @pointer = builder.alloca(type)
18
+ end
19
+ builder.store(value, @pointer)
20
+ end
21
+
22
+ def value(builder)
23
+ builder.load(@pointer)
24
+ end
25
+
26
+ end
27
+
28
+ class GlobalVariable < Variable
29
+
30
+ def initialize(name, value, mod, builder)
31
+ @name = name
32
+ @module = mod
33
+ @type = LLVM::Type(value)
34
+ @pointer = @module.globals.add(@type, @name)
35
+ @pointer.initializer = value
36
+ end
37
+
38
+ def assign(value, builder)
39
+ @type = LLVM::Type(value)
40
+ builder.store(value, @pointer)
41
+ end
42
+
43
+ def value(builder)
44
+ builder.load(@module.globals.named(@name))
45
+ end
46
+
47
+ end
48
+
49
+ class GlobalVariables
50
+
51
+ def self.init(mod)
52
+ @module ||= mod
53
+ @variables ||= {}
54
+ end
55
+
56
+ def self.add(name, value, builder)
57
+ return @variables[name] = GlobalVariable.new(name, value, @module, builder)
58
+ end
59
+
60
+ def self.named(name)
61
+ return @variables[name]
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ require "rubygems"
2
+
3
+ =begin # Custom LLVM Loading (for me only)
4
+ require "llvm/load"
5
+ LLVM.load("#{File.dirname(__FILE__)}/../llvm-2.9")
6
+ =end
7
+
8
+ require 'llvm/core'
9
+ require 'llvm/execution_engine'
10
+ require 'llvm/transforms/scalar'
11
+
12
+ module Citrus
13
+
14
+ LLVM.init_x86
15
+
16
+ PCHAR = LLVM::Type.pointer(LLVM::Int8)
17
+ FLOAT = LLVM::Double
18
+ INT = LLVM::Int
19
+ BOOL = LLVM::Int1
20
+
21
+ CMP_MAPPING = { "==" => :eq, "!=" => :ne, ">" => :gt,
22
+ ">=" => :ge, "<" => :lt, "<=" => :le }
23
+
24
+ EQU_MAPPING = { "+" => :add, "-" => :sub, "/" => :div, "*" => :mul }
25
+
26
+ DEC_MAPPING = { :bool => BOOL, :float => FLOAT, :int => INT, :string => PCHAR }
27
+
28
+ ERR_GLOBAL = "$!"
29
+ STS_GLOBAL = "$?"
30
+
31
+ end
@@ -0,0 +1,33 @@
1
+ module Citrus
2
+ class SyntaxError < RuntimeError
3
+ def initialize
4
+ line = $parser.failure_line
5
+ col = $parser.failure_column
6
+ str = $parser.input.lines.to_a[line-1]
7
+ str = str.slice(col-1, str.length).delete($/)
8
+ str = "file end" if str.empty?
9
+ puts $parser.failure_reason
10
+ super("Unexpected #{str} at #{line}:#{col}.")
11
+ end
12
+ end
13
+
14
+ class NameError < RuntimeError
15
+ def initialize(name, index=nil, ff=false)
16
+ index ||= $parser.input.index(name)
17
+ line = $parser.input.line_of(index)
18
+ col = $parser.input.column_of(index)
19
+ super("Undefined #{ff ? "" : "local variable or "}function '#{name}' at #{line}:#{col}.")
20
+ end
21
+ end
22
+
23
+ class NotFoundError < StandardError
24
+ def initialize(file)
25
+ super("No such file or directory - #{file}.")
26
+ end
27
+ end
28
+
29
+ def self.error(error)
30
+ puts("#{error.message} (#{error.class.to_s.split('::').last})")
31
+ exit(1)
32
+ end
33
+ end
@@ -0,0 +1,238 @@
1
+ module Citrus
2
+ class ::Treetop::Runtime::SyntaxNode
3
+ def value
4
+ text_value
5
+ end
6
+
7
+ def codegen(context)
8
+ end
9
+ end
10
+ Node = Treetop::Runtime::SyntaxNode
11
+
12
+ class Script < Node
13
+ def compile(compiler)
14
+ compiler.preamble
15
+ expressions.each { |e| e.codegen(compiler.generator) }
16
+ compiler.finish
17
+ end
18
+ end
19
+
20
+ class Expression < Node
21
+ def codegen(g)
22
+ statements.map { |s| s.codegen(g) }
23
+ end
24
+ end
25
+
26
+ class Assign < Node
27
+ def codegen(g)
28
+ g.assign(var.value, expression.codegen(g).last)
29
+ end
30
+ end
31
+
32
+ class GlobalEq < Node
33
+ def codegen(g)
34
+ g.assign_global(globalvar.value, expression.codegen(g).last)
35
+ end
36
+ end
37
+
38
+ class IndexEq < Node
39
+ def codegen(g)
40
+ g.assign_index(index.var.value, index.expression.codegen(g).last, expression.codegen(g).last)
41
+ end
42
+ end
43
+
44
+ class Cmp < Node
45
+ def codegen(g)
46
+ g.compare(op.value, object.codegen(g), expression.codegen(g).last)
47
+ end
48
+ end
49
+
50
+ class Equation < Node
51
+ def codegen(g)
52
+ g.equate(op.value, object.codegen(g), expression.codegen(g).last)
53
+ end
54
+ end
55
+
56
+ class Require < Node
57
+ def codegen(g)
58
+ file = string.value
59
+ file = File.join(File.dirname($file), file) unless $file.nil?
60
+ error(NotFoundError.new(file)) unless File.exists?(file)
61
+ if node = $parser.parse(File.read(file))
62
+ node.expressions.each { |e| e.codegen(g) }
63
+ else
64
+ error(SyntaxError.new)
65
+ end
66
+ end
67
+ end
68
+
69
+ class Call < Node
70
+ def codegen(g)
71
+ arg_values = calllist.args.map { |arg| arg.codegen(g) }
72
+ begin
73
+ g.call(func.value, *arg_values)
74
+ rescue
75
+ Citrus.error(NameError.new(func.value, self.interval.first, true))
76
+ end
77
+ end
78
+ end
79
+
80
+ class Def < Node
81
+ def codegen(g)
82
+ args = arglist.args.map do |arg|
83
+ [arg.var.value, arg.default.nil? ? nil : arg.default.codegen(g)]
84
+ end
85
+ g.function(func.value, args) do |gf|
86
+ expressions.each { |e| e.codegen(gf) }
87
+ end
88
+ end
89
+ end
90
+
91
+ class Return < Node
92
+ def codegen(g)
93
+ g.return(expression.codegen(g).last)
94
+ end
95
+ end
96
+
97
+ class Begin < Node
98
+ def codegen(g)
99
+ expressions.each { |e| e.codegen(g) }
100
+ rb = g.block do |gb|
101
+ rescue_expressions.each { |e| e.codegen(gb) }
102
+ end
103
+ elb = g.block do |gb|
104
+ else_expressions.each { |e| e.codegen(gb) }
105
+ end
106
+ enb = g.block do |gb|
107
+ ensure_expressions.each { |e| e.codegen(gb) }
108
+ end
109
+ g.begin(rb, elb, enb)
110
+ end
111
+ end
112
+
113
+ class If < Node
114
+ def codegen(g)
115
+ tb = g.block do |gb|
116
+ expressions.each { |e| e.codegen(gb) }
117
+ end
118
+ elfs = elsifs.each do |elf|
119
+ [elf.condition.codegen(g).last,
120
+ g.block do |gb|
121
+ elf.elements.each { |e| e.expression.codegen(gb) }
122
+ end]
123
+ end
124
+ fb = g.block do |gb|
125
+ else_expressions.each { |e| e.codegen(gb) }
126
+ end
127
+ g.condition(condition.codegen(g).last, tb, fb, elfs)
128
+ end
129
+ end
130
+
131
+ class Unless < Node
132
+ def codegen(g)
133
+ tb = g.block do |gb|
134
+ expressions.each { |e| e.codegen(gb) }
135
+ end
136
+ fb = g.block do |gb|
137
+ else_expressions.each { |e| e.codegen(gb) }
138
+ end
139
+ g.condition(g.not(condition.codegen(g).last), tb, fb)
140
+ end
141
+ end
142
+
143
+ class Case < Node
144
+ def codegen(g)
145
+ cases = {}
146
+ whens.each do |w|
147
+ cases[w.val.codegen(g).last] = g.block do |gb|
148
+ w.elements.each { |e| e.expression.codegen(gb) }
149
+ end
150
+ end
151
+ eb = g.block do |gb|
152
+ else_expressions.each { |e| e.codegen(gb) }
153
+ end
154
+ g.case(switch.codegen(g).last, cases, eb)
155
+ end
156
+ end
157
+
158
+ class While < Node
159
+ def codegen(g)
160
+ g.preploop(:while)
161
+ g.while(condition.codegen(g).last) do |gw|
162
+ expressions.each { |e| e.codegen(gw) }
163
+ end
164
+ end
165
+ end
166
+
167
+ class For < Node
168
+ def codegen(g)
169
+ ary = indices.codegen(g)
170
+ g.preploop(:for, var.value, ary)
171
+ g.for(var.value, ary) do |gf|
172
+ expressions.each { |e| e.codegen(gf) }
173
+ end
174
+ end
175
+ end
176
+
177
+ class Index < Node
178
+ def codegen(g)
179
+ g.load_index(var.codegen(g), expression.codegen(g).last)
180
+ end
181
+ end
182
+
183
+ class GlobalVar < Node
184
+ def codegen(g)
185
+ g.load_global(value)
186
+ end
187
+ end
188
+
189
+ class Var < Node
190
+ def codegen(g)
191
+ begin
192
+ g.load(value)
193
+ rescue NoMethodError
194
+ begin
195
+ g.call(value)
196
+ rescue NoMethodError
197
+ Citrus.error(NameError.new(value, self.interval.first))
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ class ArrayNode < Node
204
+ def codegen(g)
205
+ g.array(value.map{ |v| v.codegen(g).last })
206
+ end
207
+ end
208
+
209
+ class StringNode < Node
210
+ def codegen(g)
211
+ g.string(value)
212
+ end
213
+ end
214
+
215
+ class SymbolNode < Node
216
+ def codegen(g)
217
+ g.string(value)
218
+ end
219
+ end
220
+
221
+ class FloatNode < Node
222
+ def codegen(g)
223
+ g.float(value)
224
+ end
225
+ end
226
+
227
+ class NumberNode < Node
228
+ def codegen(g)
229
+ g.number(value)
230
+ end
231
+ end
232
+
233
+ class BoolNode < Node
234
+ def codegen(g)
235
+ g.bool(value)
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,32 @@
1
+ module Citrus
2
+ module Runtime
3
+
4
+ def self.build_lib(generator)
5
+ @ct = generator
6
+ build_clib
7
+ build_globals
8
+ build_raise
9
+ end
10
+
11
+ def self.build_clib
12
+ @ct.declare(:printf, [:string], :int, true)
13
+ @ct.declare(:puts, [:string], :int)
14
+ @ct.declare(:read, [:int, :string, :int], :int)
15
+ @ct.declare(:exit, [:int], :int)
16
+ end
17
+
18
+ def self.build_globals
19
+ @ct.assign_global(ERR_GLOBAL, @ct.string(""))
20
+ @ct.assign_global(STS_GLOBAL, @ct.number(0))
21
+ end
22
+
23
+ def self.build_raise
24
+ func = @ct.function("raise", ["msg"]) do |gf|
25
+ gf.assign_global(ERR_GLOBAL, gf.load("msg"))
26
+ gf.assign_global(STS_GLOBAL, gf.number(1))
27
+ end
28
+ func.force_types([PCHAR], INT)
29
+ end
30
+
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: citrus-compiler
3
+ version: !ruby/object:Gem::Version
4
+ hash: 63
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 0
10
+ version: 0.8.0
11
+ platform: ruby
12
+ authors:
13
+ - Mac Malone
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-02 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: treetop
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 21
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 9
33
+ version: 1.4.9
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: ruby-llvm
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 41
45
+ segments:
46
+ - 2
47
+ - 9
48
+ - 1
49
+ version: 2.9.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description:
53
+ email:
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ - LICENSE
61
+ files:
62
+ - lib/citrus/compiler/block.rb
63
+ - lib/citrus/compiler/function.rb
64
+ - lib/citrus/compiler/generator.rb
65
+ - lib/citrus/compiler/global_strings.rb
66
+ - lib/citrus/compiler/variable.rb
67
+ - lib/citrus/compiler.rb
68
+ - lib/citrus/core.rb
69
+ - lib/citrus/exceptions.rb
70
+ - lib/citrus/nodes.rb
71
+ - lib/citrus/runtime.rb
72
+ - lib/citrus.rb
73
+ - bin/citrus.rb
74
+ - example/factorial.ct
75
+ - example/simple.ct
76
+ - README.rdoc
77
+ - LICENSE
78
+ homepage: http://github.com/tophat/citrus
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements:
105
+ - LLVM v2.9
106
+ rubyforge_project:
107
+ rubygems_version: 1.7.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Linear compiler written in Ruby
111
+ test_files: []
112
+