123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 | -- Type checking/inference logic
concat = table.concat
import log, viz, print_err, node_assert, node_error, each_tag from require 'util'
local Int,Int32,Int16,Int8,Num,Num32,OptionalType,NilType,TypeValue
class Type
is_a: (cls)=> @ == cls or @.__class == cls or cls\contains @
works_like_a: (cls)=> @ == cls or @.__class == cls or cls\contains @
is_numeric: => @is_a(Int) or @is_a(Num) or @is_a(Int32) or @is_a(Int16) or @is_a(Int8) or @is_a(Num32)
contains: (other)=> @ == other
base_type: 'l'
load: 'loadl'
store: 'storel'
bytes: 8
nil_value: 0x7FFFFFFFFFFFFFFF
id_str: => tostring(@)\gsub('[^%w%d.]','')
__eq: (other)=> type(other) == type(@) and other.__class == @__class and tostring(other) == tostring(@)
verbose_type: => "#{@}"
orelse: (other_type)=>
if other_type == nil
return @
elseif @ == other_type
return @
elseif other_type\is_a(@)
return @
elseif @is_a(other_type)
return other_type
elseif @ == NilType
return OptionalType(other_type)
elseif other_type == NilType
return OptionalType(@)
class NamedType extends Type
new: (@name)=>
__tostring: => @name
__eq: Type.__eq
Value = NamedType("Value")
Value.contains = (other)=> other.bytes == @bytes
Value.is_a = (other)=> other == @ or other == @__class
Value.nil_value = 0x7FFFFFFFFFFFFFFF
Value32 = NamedType("Value32")
Value32.contains = (other)=> other.bytes == @bytes
Value32.is_a = (other)=> other == @ or other == @__class
Value32.base_type = 'w'
Value32.load = 'loadsw'
Value32.store = 'storew'
Value32.bytes = 4
Value32.nil_value = 0x7FFFFFFF
Value16 = NamedType("Value16")
Value16.contains = (other)=> other.bytes == @bytes
Value16.is_a = (other)=> other == @ or other == @__class
Value16.base_type = 'w'
Value16.load = 'loadsh'
Value16.store = 'storeh'
Value16.bytes = 2
Value16.nil_value = 0x7FFF
Value8 = NamedType("Value8")
Value8.contains = (other)=> other.bytes == @bytes
Value8.is_a = (other)=> other == @ or other == @__class
Value8.base_type = 'w'
Value8.load = 'loadsb'
Value8.store = 'storeb'
Value8.bytes = 1
Value8.nil_value = 0x7F
class DerivedType extends Type
new: (@name, @derived_from)=>
@base_type = @derived_from.base_type
@load = @derived_from.load
@store = @derived_from.store
@bytes = @derived_from.bytes
@nil_value = @derived_from.nil_value
__tostring: => @name
verbose_type: => "(#{@name}:#{@derived_from\verbose_type!})"
__eq: Type.__eq
is_a: (cls)=> @ == cls or @.__class == cls or cls\contains(@)
works_like_a: (cls)=> @ == cls or cls == @derived_from or @.__class == cls or cls\contains @
class MeasureType extends Type
new: (@units)=>
if type(@units) == "table"
@units = @units.str
is_numeric: => true
normalized: => @units == "" and assert(Num) or @
base_type: 'd'
load: 'loadd'
store: 'stored'
__tostring: => "<#{@units}>"
__eq: Type.__eq
is_a: (cls)=> @ == cls or @.__class == cls or cls\contains @
class ListType extends Type
new: (@item_type)=>
__tostring: => "[#{@item_type}]"
id_str: => "#{@item_type\id_str!}.List"
__eq: Type.__eq
is_a: (cls)=> cls == @ or cls == @__class or (cls.__class == ListType and @item_type\is_a(cls.item_type)) or cls\contains(@) or cls\contains(@)
works_like_a: (cls)=> cls == @ or cls == @__class or (cls.__class == ListType and @item_type\works_like_a(cls.item_type)) or cls\contains(@) or cls\contains(@)
nil_value: 0
class TableType extends Type
new: (@key_type, @value_type)=>
assert @key_type and @value_type
__tostring: => "{#{@key_type}=#{@value_type}}"
id_str: => "#{@key_type\id_str!}.#{@value_type\id_str!}.Table"
is_a: (cls)=> cls == @ or cls == @__class or (cls.__class == TableType and @key_type\is_a(cls.key_type) and @value_type\is_a(cls.value_type)) or cls\contains(@)
works_like_a: (cls)=> cls == @ or cls == @__class or (cls.__class == TableType and @key_type\works_like_a(cls.key_type) and @value_type\works_like_a(cls.value_type)) or cls\contains(@)
__eq: Type.__eq
nil_value: 0
class FnType extends Type
new: (@arg_types, @return_type, @arg_names=nil)=>
if @arg_names
@arg_types_by_name = {name,@arg_types[i] for i,name in ipairs @arg_names}
__tostring: => "#{@arg_signature!}=>#{@return_type}"
__eq: Type.__eq
nil_value: 0
id_str: => "Fn"
arg_signature: => "(#{concat ["#{a}" for a in *@arg_types], ","})"
matches: (arg_types, return_type=nil)=>
if arg_types == "*"
_=0 -- Free pass
elseif @arg_names
unmatched = {name,@arg_types[i] for i,name in ipairs @arg_names}
for name,t in pairs arg_types
continue unless type(name) == 'string'
return false, "Not a valid argument name: #{name}" unless unmatched[name]
return false, "Expected #{name} to be #{unmatched[name]}, not #{t}" unless t\is_a(unmatched[name])
unmatched[name] = nil
i,j = 1,1
while i <= #arg_types and j <= #@arg_names
t = unmatched[@arg_names[j]]
if t
return false, "Expected #{@arg_names[j]} to be #{arg_types[i]}, not #{t}" unless arg_types[i]\is_a(t)
unmatched[@arg_names[j]] = nil
i += 1
j += 1
unless @varargs
for name,t in pairs unmatched
return false, "Missing argument: #{name}" unless t\is_a(OptionalType)
else
return false, "Wrong number of arguments, expected #{#@arg_types} but got #{#arg_types}" unless #arg_types == #@arg_types or @varargs
for i=1,#arg_types
return false, "Positional argument #{i} should be #{@arg_types[i]} not #{arg_types[i]}" unless arg_types[i]\is_a(@arg_types[i])
if return_type
return false, "Expected return type to be #{@return_type}, not #{return_type}" unless return_type == "*" or @return_type\is_a(return_type)
return true
parse_args: (args)=>
assert @arg_names
values = {}
for arg in *args
if arg.__tag == "KeywordArg"
return nil, "Not a valid argument name: #{arg.name[0]}" unless @arg_types_by_name[arg.name[0]]
values[arg.name[0]] = arg.value
unmatched = [name for name in *@arg_names when not values[name]]
for arg in *args
if arg.__tag != "KeywordArg"
return nil, "Too many arguments" unless #unmatched > 0
name = table.remove(unmatched, 1)
values[name] = arg
for u in *unmatched
unless @arg_types_by_name[u]\is_a(OptionalType)
return nil, "Missing argument: #{u}"
return values, nil
class StructType extends Type
new: (@name, members)=> -- Members: {{type=t, name="Foo"}, {type=t2, name="Baz", inline=true}, ...}
@members = {}
@sorted_members = {}
@memory_size = 0
if members
for memb in *members
@add_member memb.name, memb.type, memb.inline
add_member: (name, memtype, inline)=>
offset = @memory_size
-- Align memory:
if offset % memtype.bytes != 0
offset = offset - (offset % memtype.bytes) + memtype.bytes
@members[name] = {name: name, type: memtype, offset: offset, inline: inline}
table.insert @sorted_members, @members[name]
if inline
@memory_size = offset + (memtype.memory_size or memtype.bytes)
else
@memory_size = offset + memtype.bytes
__tostring: => if @name == "" then @verbose_type! else "#{@name}"
nil_value: 0
verbose_type: =>
mem_strs = {}
for m in *@sorted_members
table.insert mem_strs, "#{m.name and type(m.name) == 'string' and m.name..':' or ''}#{m.type}"
"#{@name}{#{concat mem_strs, ","}}"
id_str: => "#{@name}"
__eq: Type.__eq
local EnumType
class UnionType extends Type
new: (@name, @members)=> -- Members: {{type=t, name="Foo"}, {type=t2, name="Baz"}, ...}
@load = "loadl"
@store = "storel"
@base_type = "l"
@memory_size = 8
@members = {}
@num_members = 0
if members
for memb in *members
@add_member memb.name, memb.type
@enum\add_field memb.name
add_member: (name, member_type)=>
@num_members += 1
@members[name] = {type: member_type, index: @num_members}
@memory_size = math.max(@memory_size, 8+member_type.bytes)
__tostring: => "#{@name}"
nil_value: 0
verbose_type: =>
mem_strs = {}
for name,info in pairs @members
table.insert mem_strs, "#{name}:#{info.type}"
"union #{@name}{#{concat mem_strs, ","}}"
id_str: => "#{@name}"
__eq: Type.__eq
NilType = NamedType("NilType")
NilType.nil_value = 0
class OptionalType extends Type
new: (@nonnil)=>
assert @nonnil and @nonnil != NilType, "Invalid optional value: #{@nonnil}"
if @nonnil.__class == OptionalType
@nonnil = assert(@nonnil.nonnil)
@base_type = @nonnil.base_type
@load = @nonnil.load
@store = @nonnil.store
@nil_value = @nonnil.nil_value
@bytes = @nonnil.bytes
contains: (other)=> other == @ or other == NilType or (@nonnil and other\is_a(@nonnil))
__tostring: => @nonnil\is_a(FnType) and "(#{@nonnil})?" or "#{@nonnil}?"
verbose_type: => @nonnil\is_a(FnType) and "(#{@nonnil\verbose_type!})?" or "#{@nonnil\verbose_type!}?"
id_str: => "Optional.#{@nonnil\id_str!}"
__eq: Type.__eq
class EnumType extends Type
new: (@name, fields={})=>
@next_value = 0
@field_values = {}
@fields = {}
for f in *fields
@add_field(f.name, f.value)
add_field: (name, value=nil)=>
assert value >= 0 if value
table.insert @fields, name
value or= @next_value
@next_value = value + 1
@nil_value = @next_value
@field_values[name] = value
nil_value: 0
__tostring: => @name
id_str: => @name
__eq: Type.__eq
-- Primitive Types:
Pointer = NamedType("Pointer")
Pointer.nil_value = 0
Num = NamedType("Num")
Num.base_type = 'd'
Num.load = 'loadd'
Num.store = 'stored'
Num.nil_value = tonumber("0"..("1")\rep(11).."1"..("0")\rep(51), 2) -- Signaling NaN
Num32 = NamedType("Num32")
Num32.base_type = 's'
Num32.load = 'loads'
Num32.store = 'stores'
Num32.bytes = 4
Num32.nil_value = tonumber("0"..("1")\rep(8).."1"..("0")\rep(23), 2) -- Signaling NaN
Int = NamedType("Int")
Int.base_type = 'l'
Int.load = 'loadl'
Int.store = 'storel'
Int32 = NamedType("Int32")
Int32.base_type = 'w'
Int32.load = 'loadsw'
Int32.store = 'storew'
Int32.bytes = 4
Int32.nil_value = 0x7FFFFFFF
Int16 = NamedType("Int16")
Int16.base_type = 'w'
Int16.load = 'loadsh'
Int16.store = 'storeh'
Int16.bytes = 2
Int16.nil_value = 0x7FFF
Int8 = NamedType("Int8")
Int8.base_type = 'w'
Int8.load = 'loadsb'
Int8.store = 'storeb'
Int8.bytes = 1
Int8.nil_value = 0x7F
Percent = DerivedType("Percent", Num)
Percent.is_numeric = => true
Abort = NamedType("Abort")
Bool = NamedType("Bool")
Bool.base_type = 'w'
Bool.load = 'loadsb'
Bool.store = 'storeb'
Bool.bytes = 1
Bool.nil_value = 0x7F
String = NamedType("String")
String.nil_value = 0
class TypeValue extends Type
new: (@type)=>
__tostring: => tostring(@type)
__eq: Type.__eq
verbose_type: => "TypeValue(#{@type\verbose_type!})"
nil_value: 0
Range = StructType("Range", {{name:"first",type:Int},{name:"next",type:Int},{name:"last",type:Int}})
Range.item_type = Int
Range.nil_value = 0
return {
:Type, :NamedType, :ListType, :TableType, :FnType, :StructType,
:Value, :Value32, :Value16, :Value8, :Pointer, :Int, :Int32, :Int16, :Int8, :Num, :Num32, :Percent, :String, :Bool, :Abort, :NilType, :Range,
:OptionalType, :MeasureType, :TypeValue, :EnumType, :UnionType, :DerivedType,
}
|