require 'strscan'
-$debug = true
+$debug = false
def error(loc, msg)
lines = File.read(loc[0])[0..(loc[1])].split("\n")
$stderr.puts "#{loc[0]}:#{lines.length}: #{msg}"
-# raise "" if $debug
+ raise "" if $debug
# $stderr.puts "#{lines.last}"
# $stderr.puts (" " * lines.last.length) + "^"
exit 1
end
def type_name()
+ loc = location()
type = expect(:ident).text.to_sym
if peek().text == "->" then
type = [type]
type << expect(:ident).text.to_sym
end
end
+ verify_type(loc, type)
type
end
+ def verify_type(loc, type)
+ if type.is_a? Array and (type.length > 2 and type[0..-2].include? :void)
+ error("invalid use of void type", loc)
+ end
+ end
+
def unary()
Call.new(location(), nil, @prev.type, [parseLevel(:unary)])
end
etype = infer(env, expr)
error(expr.loc, "expected #{type}, recieved #{etype}") if type != etype
end
- type
+ expr.type = type
end
def self.check_ifexpr(env, expr, type)
end
def self.check_call(env, expr, type)
+ error(expr.loc, "object being applied is not a function (has type: #{type.to_s})") if not type.is_a? Array
type[0..-2].each_with_index do |t,i|
check(env, expr.args[i], t)
end
- type.last
+ expr.type = type.last
end
def self.infer(env, expr)
def self.infer_symbol(env, expr)
error(expr.loc, "undefined symbol '#{expr.name}'") if env[expr.name].nil? or not env[expr.name][:set!]
error(expr.loc, "symbol '#{expr.name}' has unknown type") if env[expr.name][:type].nil?
- env[expr.name][:type]
+ expr.type = env[expr.name][:type]
end
def self.infer_annotation(env, expr)
def self.infer_block(env, expr)
env.merge!(expr.syms)
types = expr.exprs.map {|e| infer(env, e) }
- expr.type = types.last
+ types.last
end
def self.infer_call(env, expr)
def self.infer_def(env, expr)
name = expr.args[0].name
- expr.type = env[name][:type]
- if (expr.type)
+ type = env[name][:type]
+ if (type)
error(expr.loc, "symbol '#{name}' is multiply defined in scope") if env[name][:set!]
- check(env, expr.args[1], expr.type)
+ check(env, expr.args[1], type)
else
- expr.type = infer(env, expr.args[1])
- env[name][:type] = expr.type
+ type = infer(env, expr.args[1])
+ env[name][:type] = type
end
env[name][:set!] = true
- :void
+ expr.type = :void
end
def self.infer_opcall(env, expr)
end
end
+class Codegen
+ def initialize(block)
+ @temp = 0
+ @func = 0
+ @protos = StringIO.new
+ @funcs = StringIO.new
+# gen_protos(block.syms)
+ @funcs.puts emit_toplevel(block)
+ end
+
+ def to_s()
+ @protos.string + "\n" + @funcs.string
+ end
+
+ def tempvar()
+ "__tmp#{@temp +=1}"
+ end
+
+ def mkfunc()
+ "__func#{@temp +=1}"
+ end
+
+ def type_to_s(type, name)
+ if type.is_a? Array
+ raise "unsupported"
+ else
+ "#{type.to_s} #{name}"
+ end
+ end
+
+ def emit_toplevel(block)
+ out = StringIO.new
+ out.puts "void toplevel(void)"
+ lastexpr = nil
+ out.puts "{"
+ block.exprs.each do |e|
+ lastexpr = emit(out, e)
+ end
+ out.puts "}"
+ out.string
+ end
+
+ def emit_var(out, name, type, val = nil)
+ if type.is_a? Symbol or type.is_a? String
+ out.print "#{type.to_s} #{name}"
+ out.puts (val ? " = #{val}" : "") + ";"
+ elsif type.is_a? Array
+ out.puts "#{type.last} (*#{name})(#{type[0..-2].join(",")}) = #{val};"
+ else
+ raise "unsupported"
+ end
+ name
+ end
+
+ def emit(out, e, var = nil)
+ val = nil
+ type = e.type
+ if e.is_a? Parser::Val
+ val = emit_val(out, e)
+ elsif e.is_a? Parser::Call and e.func == :set!
+ var = e.args[0].name
+ type = e.args[1].type
+ val = emit(out, e.args[1])
+ elsif e.is_a? Parser::Call
+ val = emit_call(out, e)
+ elsif e.is_a? Parser::IfExpr
+ val = emit_ifexpr(out, e)
+ elsif e.is_a? Parser::Block
+ val = emit_block(out, e)
+ elsif e.is_a? Parser::Func
+ val = emit_func(out, e)
+ else
+ raise "unsupported"
+ end
+ var ||= tempvar()
+ emit_var(out, var, type, val)
+ var
+ end
+
+ def emit_val(out, e)
+ case e.type
+ when :bool
+ e.value.to_s
+ when :int
+ e.value.to_s
+ when :string
+ e.value.to_s
+ end
+ end
+
+ def emit_call(out, e)
+ if e.func.is_a? String
+ emit_opcall(out, e)
+ else
+ raise "unsupported"
+ end
+ end
+
+ def emit_opcall(out, e)
+ args = e.args.map {|a| emit(out, a) }
+ if args.length == 2
+ "#{args[0]} #{e.func} #{args[1]}"
+ else
+ "#{e.func}#{args[0]}"
+ end
+ end
+
+ def emit_ifexpr(out, e)
+ cond = emit(out, e.cond)
+ var = tempvar()
+ emit_var(out, var, e.type)
+ out.puts "if (#{cond}) {"
+ out.puts "#{var} = #{emit(out, e.br1)};"
+ out.puts "} else {"
+ out.puts "#{var} = #{emit(out, e.br2)};"
+ out.puts "}"
+ var
+ end
+
+ def emit_block(out, e)
+ var = tempvar()
+ emit_var(out, var, e.type)
+ out.puts "{"
+ exprs = e.exprs.map do |e|
+ emit(out, e)
+ end
+ out.puts "#{var} = #{exprs.last};"
+ out.puts "}"
+ var
+ end
+
+ def emit_func(out, e)
+ newout = StringIO.new
+ var = mkfunc()
+ args = []
+ e.type[0..-2].each_with_index do |t,i|
+ args << type_to_s(t, e.args[i].name)
+ end
+ @protos.puts "#{e.type.last} #{var}(#{args.join(", ")});"
+ newout.puts "#{e.type.last} #{var}(#{args.join(", ")}) {"
+ exprs = e.body.exprs.map do |e|
+ emit(newout, e)
+ end
+ newout.puts "return #{exprs.last};" if (e.type.last != :void)
+ newout.puts "}\n\n"
+ @funcs.puts newout.string
+ var
+ end
+
+# def get_vars(bsyms)
+# syms = {}
+# bsyms.each do |k,v|
+# syms[v[:type]] ||= []
+# syms[v[:type]] << k
+# end
+# syms
+# end
+#
+# def gen_protos(syms)
+# get_vars(syms).each do |k,v|
+# if k.is_a? Symbol
+# @protos.puts "#{k.to_s} #{v.join(", ")};"
+# else
+# v.each do |name|
+# @protos.puts "#{k.last} #{name}(#{k[0..-2].join(", ")});"
+# end
+# end
+# end
+# end
+end
+
block = Parser.new("dyn.src").toplevel
-pp TypeChecker.infer(SymTable.new, block)
+block.type = TypeChecker.infer(SymTable.new, block)
+puts Codegen.new(block)
+#gen = Codegen.new
+#gen << block
+#pp block
+
+syms = {}
+block.syms.each do |k,v|
+ syms[v[:type]] ||= []
+ syms[v[:type]] << k
+end
+#pp syms
# TODO:
-# * Check for correct usage of void on function types and elsewhere...
# * Add support for structs
# * Add support for checked unions
# * Add support for arrays
-# * Implement proper scoping and shadowing of identifiers
# * Implement module system for namespacing
# * Implement generic/template system for polymorphism
\ No newline at end of file