require 'strscan'
+$debug = true
+
+class SymTable < Hash
+ def initialize(parent = nil)
+ @parent = parent
+ end
+
+ def clone
+ SymTable.new(self)
+ end
+end
+
class Lexer
Tok = Struct.new(:text, :file, :pos, :type)
SPACE = /([ \t\v\n\r]+|#.*\n)/
NUMBER = /[0-9]+(\.[0-9]+)?/
STRING = /"(\\"|[^"])*"/
ID_TYPES = {
- "nil" => :nil,
"true" => :bool,
"false" => :bool,
"if" => :if,
:ident => { prefix: :variable, infix: nil, level: :none },
"[" => { prefix: :constant, infix: :subscript, level: :call },
"{" => { prefix: :constant, infix: nil, level: :none },
- :nil => { prefix: :constant, infix: nil, level: :none },
:bool => { prefix: :constant, infix: nil, level: :none },
:int => { prefix: :constant, infix: nil, level: :none },
:string => { prefix: :constant, infix: nil, level: :none },
exprs = []
expect("(")
while (!matches(")"))
- args << expect(:ident).text
+ args << Ident.new(loc, nil, expect(:ident).text.to_sym)
expect(",") if not matches(")")
end
expect(")")
Val.new(location(), :int, @prev.text.to_f)
elsif (@prev.type == :string)
Val.new(location(), :string, @prev.text)
- elsif (@prev.type == :nil)
- Val.new(location(), :nil, nil)
elsif (@prev.type == :bool)
Val.new(location(), :bool, (@prev.text == "true"))
else
def error(str, loc = nil)
file, pos = (loc ? loc : [@lex.file, (@next || @prev).pos])
puts "#{file}:#{@lex.linenum(pos)}: #{str}"
+ raise "" if $debug
exit 1
end
def self.error(loc, msg)
lines = File.read(loc[0])[0..(loc[1])].split("\n")
$stderr.puts "#{loc[0]}:#{lines.length}: #{msg}"
+ raise "" if $debug
# $stderr.puts "#{lines.last}"
# $stderr.puts (" " * lines.last.length) + "^"
exit 1
def self.check_func(env, expr, type)
env = env.clone
type[0..-2].each_with_index do |t, i|
- env[expr.args[i]] = { type: t }
+ env[expr.args[i].name] = { :loc => expr.loc, :type => t, :set! => true }
end
check(env, expr.body, type.last)
end
elsif expr.is_a? Parser::IfExpr
infer_ifexpr(env, expr)
else
- error(expr.loc, "unable to infer type of expression")
+ error(expr.loc, "unable to determine type of expression")
end
end
end
def self.infer_symbol(env, expr)
- error(expr.loc, "undefined symbol '#{expr.name}'") if env[expr.name].nil?
+ 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]
end
end
def self.infer_def(env, expr)
- expr.type = env[expr.args[0].name][:type]
+ name = expr.args[0].name
+ expr.type = env[name][:type]
if (expr.type)
+ error(expr.loc, "symbol '#{name}' is multiply defined in scope") if env[name][:set!]
check(env, expr.args[1], expr.type)
else
expr.type = infer(env, expr.args[1])
+ env[name][:type] = expr.type
end
+ env[name][:set!] = true
+ :void
end
def self.infer_opcall(env, expr)
end
end
-parse = Parser.new("dyn.src")
block = Parser.new("dyn.src").toplevel
pp TypeChecker.infer({}, block)
# TODO:
+# * Check for correct usage of void on function types and elsewhere...
# * refine rules for use of variables before definition and mutually recursive procedures...
+# * Function args need to be defined in scope
+# * Shadowing of arguments/globals is broken
# * Add support for structs
# * Add support for checked unions
# * Add support for arrays