1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512 | Types = require 'types'
bp = require 'bp'
import assign_all_types, get_type, parse_type, bind_var, bind_type from require('typecheck')
import log, viz, id, node_assert, node_error, context_err, each_tag from require 'util'
import Measure, register_unit_alias from require 'units'
ListMethods = require 'list_methods'
concat = table.concat
has_jump = bp.compile('^_("jmp"/"jnz"/"ret")\\b ..$ $$')
local stmt_compilers, expr_compilers, CodeBuilder
nonnil_eq = (t1, t2)-> (t1.nonnil or t1) == (t2.nonnil or t2)
class Environment
new: (@filename)=>
@strings = {}
@used_names = {}
@dups = setmetatable({}, {__index:->0})
@type_code = @new_code!
@string_code = @new_code!
@fn_code = @new_code!
@main_code = @new_code!
@tostring_funcs = {}
new_code: (...)=> CodeBuilder(@, ...)
fresh_name: (base_name)=>
base_name = base_name\gsub("[^a-zA-Z0-9_.]", "_")
@dups[base_name] += 1
name = "#{base_name}.#{@dups[base_name]}"
assert not @used_names[name], "How is this used? #{name}"
@used_names[name] = true
return name
fresh_local: (suggestion="x")=> "%"..@fresh_name(suggestion)
fresh_locals: (...)=>
locals = {...}
for i,l in ipairs(locals) do locals[i] = "%"..@fresh_name(l)
return table.unpack(locals)
fresh_global: (suggestion="g")=> "$"..@fresh_name(suggestion)
fresh_label: (suggestion="label")=> "@"..@fresh_name(suggestion)
fresh_labels: (...)=>
labels = {...}
for i,l in ipairs(labels) do labels[i] = "@"..@fresh_name(l)
return table.unpack(labels)
get_string_reg: (str, suggestion="str")=>
unless @strings[str]
name = @fresh_global suggestion
@strings[str] = name
@string_code\add "data #{name} = #{@string_as_data str}\n"
return @strings[str]
string_as_data: (str)=>
chunks = tostring(str)\gsub('[^ !#-[^-~%]]', (c)->"\",b #{c\byte(1)},b\"")\gsub("\n", "\\n")
return "{b\"#{chunks}\",b 0}\n"
declare_function: (fndec)=>
@fn_code\declare_function(fndec)
get_tostring_fn: (t, scope)=>
-- HACK: these primitive values' functions only take 1 arg, but it
-- should be safe to pass them an extra callstack argument, which
-- they'll just ignore.
if t\works_like_a(Types.Int)
return "$bl_tostring_int"
elseif t\works_like_a(Types.Int32)
return "$bl_tostring_int32"
elseif t\works_like_a(Types.Int16)
return "$bl_tostring_int16"
elseif t\works_like_a(Types.Int8)
return "$bl_tostring_int8"
elseif t\works_like_a(Types.Percent)
return "$bl_tostring_percent"
elseif t\works_like_a(Types.Num)
return "$bl_tostring_float"
elseif t\works_like_a(Types.Num32)
return "$bl_tostring_float32"
elseif t\works_like_a(Types.Bool)
return "$bl_tostring_bool"
elseif t\works_like_a(Types.String) or t\is_a(Types.TypeValue)
return "$bl_string"
elseif t\works_like_a(Types.Range)
return "$bl_tostring_range"
key = tostring(t)
if @tostring_funcs[key]
return @tostring_funcs[key],false
typename = t\id_str!
fn_name = @fresh_global "tostring.#{typename}"
@tostring_funcs["#{t}"] = fn_name
reg = @fresh_local typename\lower()
-- To avoid stack overflow on self-referencing structs, pass a callstack
-- as a stack-allocated linked list and check before recursing
callstack = "%callstack"
code = @new_code!
code\add "function l #{fn_name}(#{t.base_type} #{reg}, l #{callstack}) {\n"
code\add "@start\n"
-- TODO: Check for recursive lists/tables? It probably doesn't matter,
-- since the type system currently only allows recursive types for
-- structs, not lists/tables, so cycles can only be achieved with structs.
dest = @fresh_local "string"
if t\works_like_a(Types.NilType)
code\add "#{dest} =l call $bl_string(l #{@get_string_reg("nil", "nil")})\n"
elseif t\works_like_a(Types.Abort)
code\add "#{dest} =l call $bl_string(l #{@get_string_reg("Abort", "Abort")})\n"
elseif t\works_like_a(Types.OptionalType)
nil_label,nonnil_label,end_label = @fresh_labels "optional.nil", "optional.nonnil", "optional.end"
code\check_nil t, reg, nonnil_label, nil_label
code\add_label nil_label
code\add "#{dest} =l call $bl_string(l #{@get_string_reg("nil", "nil")})\n"
code\add "jmp #{end_label}\n"
code\add_label nonnil_label
fn = @get_tostring_fn t.nonnil, scope
code\add "#{dest} =l call #{fn}(#{t.nonnil.base_type} #{reg}, l #{callstack})\n"
code\add "jmp #{end_label}\n"
code\add "#{end_label}\n"
elseif t == Types.Value or t == Types.Value32 or t == Types.Value16 or t == Types.Value8
code\add "#{dest} =l call $bl_string(l #{@get_string_reg("<#{t.name}>", t.name)})\n"
elseif t\works_like_a(Types.EnumType)
init_fields,fields_exist = @fresh_labels "make_fields", "fields_exist"
tmp = @fresh_local "fieldname"
code\add "#{tmp} =l loadl $#{t\id_str!}.fields\n"
code\add "jnz #{tmp}, #{fields_exist}, #{init_fields}\n"
code\add_label init_fields
for field in *t.fields
-- str = @fresh_local "str"
-- code\add "#{str} =l call $bl_string(l #{@get_string_reg "#{t.name}.#{field}", "#{t.name}.#{field}"})\n"
code\add "#{tmp} =l add $#{t\id_str!}.fields, #{8*t.field_values[field]}\n"
code\add "storel #{@get_string_reg "#{t.name}.#{field}", "#{t.name}.#{field}"}, #{tmp}\n"
code\add "jmp #{fields_exist}\n"
code\add "#{fields_exist}\n"
code\add "#{reg} =l mul #{reg}, 8\n"
code\add "#{tmp} =l add $#{t\id_str!}.fields, #{reg}\n"
code\add "#{dest} =l loadl #{tmp}\n"
elseif t\works_like_a(Types.ListType)
buf = @fresh_local "buf"
code\add "#{buf} =l call $CORD_from_char_star(l #{@get_string_reg "[", "lsq"})\n"
item_reg = @fresh_local "item"
code\add_for_loop {
type:t, iter_reg:reg, val_reg:item_reg,
make_body: ->
item_str = @fresh_local "item.string"
fn = @get_tostring_fn t.item_type, scope
return @new_code "#{item_str} =l call #{fn}(#{t.item_type.base_type} #{item_reg}, l #{callstack})\n",
"#{buf} =l call $CORD_cat(l #{buf}, l #{item_str})\n"
make_between: ->
return @new_code "#{buf} =l call $CORD_cat(l #{buf}, l #{@get_string_reg ", ", "comma.space"})\n"
}
code\add "#{buf} =l call $CORD_cat(l #{buf}, l #{@get_string_reg "]", "rsq"})\n"
code\add "#{buf} =l call $CORD_to_const_char_star(l #{buf})\n"
code\add "#{dest} =l call $bl_string(l #{buf})\n"
elseif t\works_like_a(Types.TableType)
buf = @fresh_local "buf"
code\add "#{buf} =l call $CORD_from_char_star(l #{@get_string_reg "{", "lbracket"})\n"
key_reg,value_reg = @fresh_locals "key","value"
code\add_for_loop {
type: t, iter_reg:reg, :key_reg, :value_reg,
make_body: ->
key_str = @fresh_local "key.string"
fn = @get_tostring_fn t.key_type, scope
local code
code = @new_code "#{key_str} =l call #{fn}(#{t.key_type.base_type} #{key_reg}, l #{callstack})\n"
code\add "#{buf} =l call $CORD_cat(l #{buf}, l #{key_str})\n"
code\add "#{buf} =l call $CORD_cat(l #{buf}, l #{@get_string_reg "=", "equals"})\n"
value_str = @fresh_local "value.string"
fn = @get_tostring_fn t.value_type, scope
code\add "#{value_str} =l call #{fn}(#{t.value_type.base_type} #{value_reg}, l #{callstack})\n"
code\add "#{buf} =l call $CORD_cat(l #{buf}, l #{value_str})\n"
return code
make_between: ->
return "#{buf} =l call $CORD_cat(l #{buf}, l #{@get_string_reg ", ", "comma"})\n"
}
code\add "#{buf} =l call $CORD_cat(l #{buf}, l #{@get_string_reg "}", "rbracket"})\n"
code\add "#{buf} =l call $CORD_to_const_char_star(l #{buf})\n"
code\add "#{dest} =l call $bl_string(l #{buf})\n"
elseif t\works_like_a(Types.StructType)
if t.name\match "^Tuple%.[0-9]+$"
code\add "#{dest} =l call $CORD_from_char_star(l #{@get_string_reg("{", "curly")})\n"
else
code\add "#{dest} =l call $CORD_from_char_star(l #{@get_string_reg("#{t.name}{", "#{t\id_str!}.name")})\n"
-- Check callstack for cyclical references
cycle_next,cycle_check,cycle_found,cycle_notfound,conclusion = @fresh_labels(
"cycle.check.next", "cycle.check", "cycle.found", "cycle.notfound", "tostring.conclusion")
walker = @fresh_local "cycle.walker"
code\add "#{walker} =l copy #{callstack}\n"
code\add "jmp #{cycle_next}\n"
code\add_label cycle_next
code\add "jnz #{walker}, #{cycle_check}, #{cycle_notfound}\n"
code\add_label cycle_check
cycle_parent = @fresh_local "cycle.parent"
code\add "#{cycle_parent} =l add #{walker}, 8\n"
code\add "#{cycle_parent} =l loadl #{cycle_parent}\n"
code\add "#{walker} =l loadl #{walker}\n"
wasfound = @fresh_local "cycle.wasfound"
code\add "#{wasfound} =l ceql #{cycle_parent}, #{reg}\n"
code\add "jnz #{wasfound}, #{cycle_found}, #{cycle_next}\n"
code\add_label cycle_found
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg "...", "ellipsis"})\n"
code\add "jmp #{conclusion}\n"
code\add_label cycle_notfound
new_callstack = @fresh_local "new.callstack"
code\add "#{new_callstack} =l alloc8 #{2*8}\n"
code\add "storel #{callstack}, #{new_callstack}\n"
p = @fresh_local "p"
code\add "#{p} =l add #{new_callstack}, 8\n"
code\add "storel #{reg}, #{p}\n"
for i,mem in ipairs t.sorted_members
if i > 1
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg ", ", "comma.space"})\n"
member_loc = @fresh_local "#{t\id_str!\lower!}.#{mem.name}.loc"
code\add "#{member_loc} =l add #{reg}, #{mem.offset}\n"
member_reg = @fresh_local "#{t\id_str!\lower!}.#{mem.name}"
if mem.inline
code\add "#{member_reg} =#{mem.type.base_type} copy #{member_loc}\n"
else
code\add "#{member_reg} =#{mem.type.base_type} #{mem.type.load} #{member_loc}\n"
if mem.name
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg("#{mem.name}=")})\n"
member_str = @fresh_local "#{t\id_str!\lower!}.#{mem.name}.string"
fn = @get_tostring_fn mem.type, scope
code\add "#{member_str} =l call #{fn}(#{mem.type.base_type} #{member_reg}, l #{new_callstack})\n"
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{member_str})\n"
code\add "jmp #{conclusion}\n"
code\add_label conclusion
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg "}", "close.curly"})\n"
code\add "#{dest} =l call $CORD_to_const_char_star(l #{dest})\n"
code\add "#{dest} =l call $bl_string(l #{dest})\n"
elseif t\works_like_a(Types.UnionType)
init_fields,fields_exist = @fresh_labels "make_fields", "fields_exist"
tmp = @fresh_local "fieldname"
code\add "#{tmp} =l loadl $#{t\id_str!}.member_names\n"
code\add "jnz #{tmp}, #{fields_exist}, #{init_fields}\n"
code\add_label init_fields
for name,info in pairs t.members
code\add "#{tmp} =l add $#{t\id_str!}.member_names, #{(info.index-1)*8}\n"
code\add "storel #{@get_string_reg "#{t.name}.#{name}", "#{t.name}.#{name}"}, #{tmp}\n"
code\add "jmp #{fields_exist}\n"
code\add "#{fields_exist}\n"
tag,offset,tmp,name,is_tag = @fresh_locals "tag","offset","tmp","name","is_tag"
val_loc = @fresh_local "val.loc"
code\add "#{tag} =l loadl #{reg}\n"
code\add "#{offset} =l sub #{tag}, 1\n"
code\add "#{offset} =l mul #{offset}, 8\n"
code\add "#{name} =l add $#{t\id_str!}.member_names, #{offset}\n"
code\add "#{dest} =l loadl #{name}\n"
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg '(', "lparen"})\n"
code\add "#{val_loc} =l add #{reg}, 8\n"
next_check,done_label = @fresh_labels "check.member","done"
for name,info in pairs t.members
check,next_check = next_check, @fresh_label "check.member"
found_member = @fresh_label "found.member"
code\add_label check
code\add "#{is_tag} =w ceql #{tag}, #{info.index}\n"
code\add "jnz #{is_tag}, #{found_member}, #{next(t.members, name) and next_check or done_label}\n"
code\add_label found_member
val = @fresh_local "val"
code\add "#{val} =#{info.type.base_type} #{info.type.load} #{val_loc}\n"
code\add "#{tmp} =l call #{@get_tostring_fn info.type, scope}(#{info.type.base_type} #{val}, l #{callstack})\n"
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{tmp})\n"
code\add "jmp #{done_label}\n"
code\add "#{done_label}\n"
code\add "#{dest} =l call $CORD_cat(l #{dest}, l #{@get_string_reg ')', "rparen"})\n"
code\add "#{dest} =l call $CORD_to_const_char_star(l #{dest})\n"
code\add "#{dest} =l call $bl_string(l #{dest})\n"
elseif t\works_like_a(Types.FnType)
code\add "#{dest} =l call $bl_string(l #{@get_string_reg("#{t}")})\n"
elseif t\works_like_a(Types.MeasureType)
code\add "#{dest} =l call $bl_tostring_float(d #{reg})\n"
code\add "#{dest} =l call $bl_string_append_string(l #{dest}, l #{@get_string_reg("<"..t.units..">", "units")})\n"
elseif t\works_like_a(Types.Pointer)
code\add "#{dest} =l call $bl_tostring_hex(l #{reg})\n"
else
error "Unsupported tostring type: #{t\verbose_type!}"
code\add "ret #{dest}\n"
code\add "}\n"
indented_code = tostring(code)\gsub("[^\n]+", =>((@\match("^[@}]") or @\match("^function")) and @ or " "..@))
@fn_code\add indented_code
return fn_name
apply_macros: (ast)=>
substitute = (ast, replacements)->
return ast unless type(ast) == 'table'
if ast.__tag == "Var" and replacements[ast[0]]
return replacements[ast[0]]
return {k,(if type(k) == 'string' and k\match("^__") then v else substitute(v, replacements)) for k,v in pairs ast}
macros = {}
h = 0
for m in coroutine.wrap(-> each_tag(ast, "Macro"))
macro_vars = {}
for dec in coroutine.wrap(-> each_tag(m.body, "Declaration"))
macro_vars[dec.var[0]] = {[0]:"#{dec.var[0]}.hygienic.#{h}", __tag:"Var"}
h += 1
for dec in coroutine.wrap(-> each_tag(m.body, "FnDecl"))
macro_vars[dec.name[0]] = {[0]:"#{dec.name[0]}.hygienic.#{h}", __tag:"Var"}
h += 1
-- TODO: struct, union, enum, etc.
macros[m.name[0]] or= {}
macros[m.name[0]][#m.args] = substitute(m, macro_vars)
apply_macros = (ast)->
return ast if type(ast) != 'table'
if ast.__tag == "Macro"
return {[0]:"pass", __tag:"Pass"}
if ast.__tag == "FnCall"
mac = macros[ast.fn[0]]
mac = mac and mac[#ast]
if mac
body = mac.body
while body.__tag == "Block" and #body == 1
body = body[1]
return apply_macros(substitute(body, {mac.args[i][0], apply_macros(ast[i]) for i=1,#ast}))
return {k,apply_macros(v) for k,v in pairs(ast) when k != "__parent"}
ast = apply_macros(ast)
add_parenting = (ast)->
for k,node in pairs ast
if type(node) == 'table' and not (type(k) == 'string' and k\match("^__"))
node.__parent = ast
add_parenting node
add_parenting(ast)
log "Post macro: #{viz ast}"
return ast
compile_program: (ast, filename)=>
ast = @apply_macros(ast)
@type_code = @new_code "type :Range = {l,l,l}\n"
bind_var ast, {
[0]:"args",
__tag: "Var",
__type: Types.ListType(Types.String),
__location: "$args",
}
bind_var ast, {
[0]:"say",
__tag: "Var",
__type: Types.FnType({Types.String}, Types.NilType, {"text"}),
__register: "$puts",
}
for name,t in pairs(Types)
bind_type ast, {
[0]: name,
__tag: "TypeVar",
__type: Types.TypeValue(t),
}
assign_all_types ast, @
-- print "\n\n#{viz ast}"
-- Type names:
for typedecl in coroutine.wrap(-> each_tag(ast, "EnumDeclaration","StructDeclaration","UnionDeclaration","TypeDeclaration"))
t = get_type(typedecl)
type_name = "$#{t.type\id_str!}.name"
@type_code\add "data #{type_name} = #{@string_as_data tostring(t.type)}\n"
typedecl.name.__register = type_name
-- Enum field names
for e in coroutine.wrap(-> each_tag(ast, "EnumDeclaration"))
t = get_type(e)
enum_t = t.type
fieldnames = "$#{enum_t\id_str!}.fields"
@type_code\add "data #{fieldnames} = {#{("l 0,")\rep(enum_t.next_value)}}\n"
-- Union field names
for u in coroutine.wrap(-> each_tag(ast, "UnionDeclaration", "UnionType"))
t = get_type(u, true).type
assert t\is_a(Types.UnionType), "#{t}"
fieldnames = "$#{t\id_str!}.member_names"
@type_code\add "data #{fieldnames} = {#{concat ["l 0" for _ in pairs t.members], ", "}}\n"
is_file_scope = (scope)->
while scope
return true if scope == ast
switch scope.__tag
when "FnDecl","Lambda","Macro","For","ConvertDecl"
return false
scope = scope.__parent
error("Unexpectedly reached a node not parented to top-level AST")
file_scope_vars = {}
-- Struct/union static variables
for decl in coroutine.wrap(-> each_tag(ast, "UnionDeclaration", "StructDeclaration"))
t = get_type(decl, true).type
for staticvar in *decl
continue unless staticvar.__tag == "Declaration"
staticvar.var.__location = @fresh_global "#{t\id_str!}.#{staticvar.var[0]}"
table.insert file_scope_vars, staticvar.var
-- Set up variable registers:
for v in coroutine.wrap(-> each_tag(ast, "Var"))
node_assert v.__declaration, v, "No declaration found"
continue unless v == v.__declaration
continue if v.__register or v.__location
if v.__parent.__tag == "FnDecl" and v == v.__parent.name
v.__register = @fresh_global v[0]
elseif is_file_scope(v)
v.__location = @fresh_global v[0]
table.insert file_scope_vars, v
else
v.__register = @fresh_local v[0]
for v in coroutine.wrap(-> each_tag(ast, "Var"))
continue if v.__register or v.__location
node_assert v.__declaration, v, "No declaration found"
v.__register = v.__declaration.__register
v.__location = v.__declaration.__location
node_assert v.__register or v.__location, v, "Couldn't figure out what this variable refers to"
-- Compile modules:
for use in coroutine.wrap(-> each_tag(ast, "Use"))
error "Not implemented"
module_dirname,module_basename = use.name[0]\match("(.*/)([^/]*)$")
if not module_dirname
module_dirname,module_basename = "",modname
found = false
for search_path in (os.getenv("BLANG_MODULE_PATHS") or ".")\gmatch("[^:]+")
unless search_path\match("^/")
dirname = filename\match("^.*/") or ""
search_path = dirname..search_path
path = "#{search_path}/#{module_dirname}/#{module_basename}"
libfile = io.open((path\gsub("([^/]+)$", "lib%1.so")))
if libfile
libfile\close!
found = true
break
bl_path = path\gsub("([^/]+)$", "%1.bl")
bl_file = io.open(bl_path)
if bl_file
bl_file\close!
assert os.execute("./blangc -c #{bl_path}"), "Failed to compile dependency module: #{bl_path}"
found = true
break
assert found, "Failed to find module: #{use.name[0]}"
naked_imports = {}
-- Hook up naked imports
for use in coroutine.wrap(-> each_tag(ast, "Use"))
error "Not implemented"
continue if use.as
i = 1
while use.__parent[i] != use
i += 1
scope = {table.unpack(use.__parent, i+1)}
mod_type = get_type(use)
use.__imports = {}
for i,mem in ipairs((mod_type.nonnil or mod_type).sorted_members)
loc = @fresh_global "#{use.name[0]}.#{mem.name}"
t = use.orElse and mem.type or Types.OptionalType(mem.type)
pseudo_var = setmetatable({[0]: mem.name, __tag:"Var", __type: t, __location: loc, __from_use:true}, getmetatable(use))
use.__imports[i] = pseudo_var
fn_type = mem.type\is_a(Types.FnType) and mem.type or nil
hook_up_refs pseudo_var, scope, fn_type
table.insert naked_imports, pseudo_var
-- Conversion functions
bind_conversion = (fn)=>
switch @__tag
when "As"
src_t, dest_t = @expr.__type, parse_type(@type)
if src_t and dest_t and fn.__type\matches({src_t}, dest_t)
@__converter = fn
bind_conversion @expr, fn
when "Interp"
src_t, dest_t = @value.__type, @__parent.__parent.__type
if src_t and dest_t and fn.__type\matches({src_t}, dest_t)
@__converter = fn
bind_conversion @value, fn
else
for k,child in pairs @
continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
bind_conversion child, fn
for conv in coroutine.wrap(-> each_tag(ast, "ConvertDecl"))
conv.__register = @fresh_global "convert"
bind_conversion conv.__parent, conv
-- Compile functions:
for lambda in coroutine.wrap(-> each_tag(ast, "Lambda"))
lambda.__register = @fresh_global "lambda"
for fndec in coroutine.wrap(-> each_tag(ast, "FnDecl", "Lambda", "ConvertDecl"))
@declare_function fndec
exports = {}
for exp in coroutine.wrap(-> each_tag(ast, "Export"))
for var in *exp
table.insert(exports, var)
body_code = @new_code!
body_code\add_stmt ast
code = @new_code "# Source file: #{filename}\n\n"
code\add @type_code if #@type_code.chunks > 0
code\add @string_code if #@string_code.chunks > 0
code\add "data $args = {l 0}\n"
code\add "data $exports = {#{concat [get_type(e).base_type.." 0" for e in *exports], ","}}\n"
for var in *naked_imports
code\add "data #{var.__location} = {#{get_type(var).base_type} 0}\n"
for var in *file_scope_vars
code\add "data #{var.__location} = {#{get_type(var).base_type} 0}\n"
code\add "#{@fn_code}\n" if #@fn_code.chunks > 0
code\add "export function l $load() {\n"
code\add "@start\n"
-- TODO: check for top-level returns and error if they exist
indented_code = tostring(body_code)\gsub("[^\n]+", =>((@\match("^[@}]") or @\match("^function")) and @ or " "..@))
code\add indented_code
for fndec in coroutine.wrap(-> each_tag(ast, "FnDecl"))
if fndec.name[0] == "main" and #fndec.args == 0
code\add " call #{fndec.name.__register}()\n"
break
for i,e in ipairs exports
var_reg,var_code = @to_reg e
code\add var_code
dest = @fresh_local "export.loc"
code\add " #{dest} =l add $exports, #{(i-1)*8}\n"
code\add " storel #{var_reg}, #{dest}\n"
code\add " ret $exports\n"
code\add "}\n"
code\add "export function w $main(w %argc, l %argv) {\n"
code\add "@start\n"
code\add " %argc2 =l extsw %argc\n"
code\add " %args.size =l mul %argc2, 8\n"
code\add " %args.items =l call $gc_alloc(l %args.size)\n"
code\add " call $memcpy(l %args.items, l %argv, l %args.size)\n"
code\add " %args =l call $gc_alloc(l 16)\n"
code\add " storel %argc2, %args\n"
code\add " storel %args, $args\n"
code\add " %args =l add %args, 8\n"
code\add " storel %args.items, %args\n"
code\add " call $load()\n"
code\add " ret 0\n"
code\add "}\n"
source_chunks = ast[0]\gsub('[^ !#-[^-~]', (c)->"\",b #{c\byte(1)},b\"")\gsub("\n", "\\n")
code\add "\nexport data $source = {b\"#{source_chunks}\",b 0}\n"
return code
class CodeBuilder
new: (@env, ...)=>
@chunks = {}
@add ...
add: (...)=>
for i=1,select("#",...)
table.insert(@chunks, (select(i, ...)))
return @
new_code: (...)=> @env\new_code(...)
__tostring: => table.concat([tostring(chunk) for chunk in *@chunks])
ends_with_jump: =>
last = @chunks[#@chunks]
if type(last) == 'table'
return last\ends_with_jump!
elseif type(last) == 'string'
return has_jump\match(last)
get_string_reg: (...)=> @env\get_string_reg(...)
get_tostring_fn: (...)=> @env\get_tostring_fn(...)
fresh_name: (...)=> @env\fresh_name(...)
fresh_local: (...)=> @env\fresh_local(...)
fresh_locals: (...)=> @env\fresh_locals(...)
fresh_global: (...)=> @env\fresh_global(...)
fresh_label: (...)=> @env\fresh_label(...)
fresh_labels: (...)=> @env\fresh_labels(...)
add_values: (...)=>
nodes = {...}
regs = {}
for node in *nodes
assert expr_compilers[node.__tag], "Expression compiler not implemented for #{node.__tag}"
reg = expr_compilers[node.__tag](@, node)
table.insert(regs, reg)
return table.unpack(regs)
add_value: (...)=> @add_values(...)
add_label: (label)=> @add label, "\n"
declare_function: (fndec)=>
args = ["#{parse_type(arg.type).base_type} #{arg.name.__register}" for arg in *fndec.args]
if fndec.selfVar
t = get_type fndec.selfVar, true
table.insert args, 1, "#{t.base_type} #{fndec.selfVar.__register}"
fn_type = get_type fndec
ret_type = fn_type.return_type
check_returns = (ast)->
return if type(ast) != 'table'
return if ast.__tag == "FnDecl" or ast.__tag == "Lambda" or ast.__tag == "Macro"
if ast.__tag == "Return"
unless (ret_type == Types.NilType and not ast.value) or get_type(ast.value)\is_a(ret_type)
node_error ast, "Not a valid function return value. Expected type is #{ret_type}, not #{ast.value and get_type(ast.value) or Types.NilType}"
for k,v in pairs ast
check_returns(v) if type(v) == 'table' and not (type(k) == 'string' and k\match("^__"))
check_returns fndec.body
fn_name = fndec.__register or node_assert fndec.name.__register, fndec, "Function has no name"
fn_code = @new_code!
fn_code\add "\nfunction #{ret_type\is_a(Types.Abort) and "" or ret_type.base_type.." "}"
fn_code\add "#{fn_name}(#{concat args, ", "}) {\n"
fn_code\add "@start\n"
body_code = @new_code!
if fndec.__tag == "Lambda" or fndec.body.__tag != "Block"
ret_reg = body_code\add_values fndec.body
body_code\add "ret #{ret_reg}\n"
else
body_code\add_stmt fndec.body
body_code = tostring(body_code)\gsub("[^\n]+", =>(@\match("^%@") and @ or " "..@))
fn_code\add body_code
unless fn_code\ends_with_jump!
if ret_type\is_a(Types.Abort)
fn_code\add " ret\n"
else
fn_code\add " ret 0\n"
fn_code\add "}\n"
@add fn_code
add_stmt: (node)=>
assert node.__tag, "Not a node: #{viz node}"
if stmt_compilers[node.__tag]
stmt_compilers[node.__tag](@, node)
elseif expr_compilers[node.__tag]
expr_compilers[node.__tag](@, node)
else
node_error node, "Not implemented: #{node.__tag}"
check_nil: (t, reg, nonnil_label, nil_label)=>
if t == Types.NilType
@add "jmp #{nil_label}\n"
elseif not t\is_a(Types.OptionalType)
@add "jmp #{nonnil_label}\n"
elseif t.nil_value == 0
@add "jnz #{reg}, #{nonnil_label}, #{nil_label}\n"
elseif t.nonnil.base_type == "d" or t.nonnil.base_type == "s"
int_val,is_nan = @fresh_locals "int_val","is_nan"
@add "#{int_val} =l cast #{reg}\n"
@add "#{is_nan} =w ceql #{int_val}, #{t.nonnil.nil_value} # Check for nil\n"
@add "jnz #{is_nan}, #{nil_label}, #{nonnil_label}\n"
else
tmp = @fresh_local "is.nonnil"
@add "#{tmp} =w cne#{t.base_type} #{reg}, #{t.nil_value}\njnz #{tmp}, #{nonnil_label}, #{nil_label}\n"
check_truthiness: (t, reg, truthy_label, falsey_label)=>
if t\is_a(Types.Bool)
@add "jnz #{reg}, #{truthy_label}, #{falsey_label}\n"
elseif t\is_a(Types.NilType)
@add "jmp #{falsey_label}\n"
elseif t.base_type == "d"
tmp = @fresh_local "is.nonnil"
@add "#{tmp} =l cod #{reg}, d_0.0 # Test for NaN\njnz #{tmp}, #{truthy_label}, #{falsey_label}\n"
elseif t.base_type == "s"
tmp = @fresh_local "is.nonnil"
@add "#{tmp} =l cos #{reg}, d_0.0 # Test for NaN\njnz #{tmp}, #{truthy_label}, #{falsey_label}\n"
elseif t\is_a(Types.OptionalType)
if t.nonnil\is_a(Types.Bool)
tmp = @fresh_local "is.true"
@add "#{tmp} =l ceq#{t.base_type} #{reg}, 1\njnz #{tmp}, #{truthy_label}, #{falsey_label}\n"
else
@check_nil t, reg, truthy_label, falsey_label
else
@add "jmp #{truthy_label}\n"
add_for_loop: (args)=>
iter_type = assert args.type
iter_reg = args.iter_reg
key_reg = args.key_reg
value_reg = args.value_reg or args.val_reg
scope = args.scope
make_body = args.make_body
make_between = args.make_between
filter = args.filter
-- Rough breakdown:
-- i = 0 | k = NULL
-- len = #list
-- jmp @for.next
-- @for.next
-- i += 1 | k = bl_table_next(h, k)
-- jnz (i > len), @for.end, @for.body
-- @for.body
-- index = i
-- item = list[i] | bl_table_get(h, k)
-- // body code
-- i += 1 | k = bl_table_next(h, k)
-- jnz (i <= len), @for.end, @for.between
-- @for.between
-- // between code
-- jmp @for.body
-- @for.end
next_label,body_label,between_label,end_label = @fresh_labels "for.next","for.body","for.between","for.end"
if scope
for skip in coroutine.wrap(-> each_tag(scope, "Skip"))
if not skip.target or skip.target[0] == "for" or (key_reg and skip.target.__register == key_reg) or (value_reg and skip.target.__register == value_reg)
skip.jump_label = next_label
for stop in coroutine.wrap(-> each_tag(scope, "Stop"))
if not stop.target or stop.target[0] == "for" or (key_reg and stop.target.__register == key_reg) or (value_reg and stop.target.__register == value_reg)
stop.jump_label = end_label
i = @fresh_local(iter_type\works_like_a(Types.TableType) and "k" or "i")
len = @fresh_local "len"
is_done = @fresh_local "for.is_done"
@add "# For loop:\n"
if iter_type\works_like_a(Types.TableType)
@add "#{i} =l copy #{iter_type.key_type.nil_value}\n"
else
@add "#{i} =l copy 0\n"
local list_item
if iter_type\is_a(Types.Range)
@add "#{len} =l call $range_len(l #{iter_reg})\n"
elseif iter_type\is_a(Types.ListType)
@add "#{len} =l loadl #{iter_reg}\n"
list_item = @fresh_local "list.item"
@add "#{list_item} =l add #{iter_reg}, 8\n"
@add "#{list_item} =l loadl #{list_item}\n"
elseif iter_type\is_a(Types.TableType)
len = nil -- Len is not used for tables
else
error "Expected an iterable type, not #{iter_type}"
@add "jmp #{next_label}\n"
@add_label next_label
if iter_type\is_a(Types.TableType)
@add "#{i} =l call $bl_table_next(l #{iter_reg}, l #{i}, l #{iter_type.key_type.nil_value})\n"
@add "#{is_done} =w ceql #{i}, #{iter_type.key_type.nil_value}\n"
@add "jnz #{is_done}, #{end_label}, #{body_label}\n"
else
@add "#{i} =l add #{i}, 1\n"
@add "#{is_done} =w csgtl #{i}, #{len}\n"
@add "jnz #{is_done}, #{end_label}, #{body_label}\n"
@add_label body_label
TableMethods = require 'table_methods'
if iter_type\is_a(Types.TableType)
if key_reg
if iter_type.key_type.base_type == "s" or iter_type.key_type.base_type == "d"
@add "#{key_reg} =#{iter_type.key_type.base_type} cast #{i}\n"
else
@add "#{key_reg} =#{iter_type.key_type.base_type} copy #{i}\n"
if value_reg
value_bits = @fresh_local "value.bits"
@add "#{value_bits} =l call $bl_table_get(l #{iter_reg}, l #{i}, l #{iter_type.key_type.nil_value}, l #{iter_type.value_type.nil_value})\n"
if iter_type.value_type.base_type == "s" or iter_type.value_type.base_type == "d"
@add "#{value_reg} =#{iter_type.value_type.base_type} cast #{value_bits}\n"
else
@add "#{value_reg} =#{iter_type.value_type.base_type} copy #{value_bits}\n"
else
if key_reg
@add "#{key_reg} =l copy #{i}\n"
assert value_reg
if iter_type\is_a(Types.Range)
-- TODO: optimize to not use function call and just do var=start ... var += step
@add "#{value_reg} =l call $range_nth(l #{iter_reg}, l #{i})\n"
else
@add "#{value_reg} =#{iter_type.item_type.base_type} #{iter_type.item_type.load} #{list_item}\n"
@add "#{list_item} =l add #{list_item}, #{iter_type.item_type.bytes}\n"
if filter
@add_stmt filter
if make_body
@add make_body(key_reg, value_reg)
else
error "No body"
-- If we reached this point, no skips
unless @ends_with_jump!
if iter_type\is_a(Types.TableType)
@add "#{i} =l call $bl_table_next(l #{iter_reg}, l #{i}, l #{iter_type.key_type.nil_value})\n"
@add "#{is_done} =w ceql #{i}, #{iter_type.key_type.nil_value}\n"
@add "jnz #{is_done}, #{end_label}, #{between_label}\n"
else
@add "#{i} =l add #{i}, 1\n"
@add "#{is_done} =w csgtl #{i}, #{len}\n"
@add "jnz #{is_done}, #{end_label}, #{between_label}\n"
@add_label between_label
if make_between
@add make_between(key_reg, value_reg)
unless @ends_with_jump!
@add "jmp #{body_label}\n"
@add_label end_label
add_infix_op: (ast, op)=>
assert ast.lhs and ast.rhs, "Infix node doesn't have lhs/rhs: #{viz ast}"
t = get_type ast.lhs
lhs_reg = @add_values ast.lhs
ret_reg = @fresh_local "result"
rhs = ast.rhs
rhs_type = get_type rhs
-- node_assert nonnil_eq(rhs_type, t), rhs, "Expected type: #{t} but got type #{rhs_type}"
rhs_reg = @add_values rhs
if type(op) == 'string'
@add "#{ret_reg} =#{t.base_type} #{op} #{lhs_reg}, #{rhs_reg}\n"
else
@add op(ret_reg, lhs_reg, rhs_reg)
return ret_reg
add_comparison: (ast, cmp)=>
t1 = get_type ast.lhs
t2 = get_type ast.rhs
node_assert t1 == t2, ast, "This comparison is between two different types: `#{t1}` and `#{t2}` which is not allowed"
lhs_reg, rhs_reg = @add_values ast.lhs, ast.rhs
result = @fresh_local "comparison"
if t1\is_a(Types.String)
tmp = @fresh_local "comparison.i32"
@add "#{tmp} =w call $strcmp(l #{lhs_reg}, l #{rhs_reg})\n"
@add "#{result} =w csltw #{tmp}, 0\n"
else
@add "#{result} =w #{cmp} #{lhs_reg}, #{rhs_reg}\n"
return result
add_repeat_loop: (ast, make_body)=>
-- Rough breakdown:
-- jmp @repeat
-- @repeat
-- // body code
-- jmp @repeat.between
-- // between code
-- jmp @repeat
-- @repeat.end
repeat_label,between_label,end_label = @fresh_labels "repeat", "repeat.between", "repeat.end"
for skip in coroutine.wrap(-> each_tag(ast, "Skip"))
if not skip.target or skip.target[0] == "repeat"
skip.jump_label = repeat_label
for stop in coroutine.wrap(-> each_tag(ast, "Stop"))
if not stop.target or stop.target[0] == "repeat"
stop.jump_label = end_label
@add "jmp #{repeat_label}\n"
@add_label repeat_label
@add_stmt ast.filter if ast.filter
if make_body
@add make_body!
else
@add_stmt(ast.body)
if ast.between
unless @ends_with_jump!
@add "jmp #{between_label}\n"
@add_label between_label
@add_stmt(ast.between)
unless @ends_with_jump!
@add "jmp #{repeat_label}\n"
@add_label end_label
add_while_loop: (ast, make_body)=>
-- Rough breakdown:
-- jmp @while.condition
-- jnz (condition), @while.body, @while.end
-- @while.body
-- // body code
-- jmp @while.between
-- // between code
-- jnz (condition), @while.body, @while.end
-- @while.end
cond_label,body_label,between_label,end_label = @fresh_labels "while.condition", "while.body", "while.between", "while.end"
for skip in coroutine.wrap(-> each_tag(ast, "Skip"))
if not skip.target or skip.target[0] == "while"
skip.jump_label = cond_label
for stop in coroutine.wrap(-> each_tag(ast, "Stop"))
if not stop.target or stop.target[0] == "while"
stop.jump_label = end_label
@add "jmp #{cond_label}\n"
@add_label cond_label
cond_reg = @add_values ast.condition
@add "jnz #{cond_reg}, #{body_label}, #{end_label}\n"
@add_label body_label
@add_stmt ast.filter if ast.filter
if make_body
@add make_body!
else
@add_stmt(ast.body)
if ast.between
cond_reg2 = @add_values ast.condition
unless @ends_with_jump!
@add "jnz #{cond_reg2}, #{between_label}, #{end_label}\n"
@add_label between_label
@add_stmt ast.between
unless @ends_with_jump!
@add "jmp #{body_label}\n"
else
unless @ends_with_jump!
@add "jmp #{cond_label}\n"
@add_label end_label
set_nil: (t, reg)=>
if t.base_type == "s" or t.base_type == "d"
@add "#{reg} =#{t.base_type} cast #{t.nil_value} # Set to nil\n"
else
@add "#{reg} =#{t.base_type} copy #{t.nil_value} # Set to nil\n"
expr_compilers =
Var: (ast)=>
return ast.__register if ast.__register
t = get_type(ast)
if ast.__location
tmp = @fresh_local "#{ast[0]}.value"
@add "#{tmp} =#{t.base_type} #{t.load} #{ast.__location}\n"
return tmp
elseif t\is_a(Types.TypeValue)
return @get_string_reg(t.type\verbose_type!, ast[0])
node_error ast, "This variable is not defined"
Declaration: (ast)=>
value_type = get_type ast.value, true
val_reg = @add_value ast.value
if ast.var.__register
@add "#{ast.var.__register} =#{value_type.base_type} copy #{val_reg}\n"
elseif ast.var.__location
@add "#{value_type.store} #{val_reg}, #{ast.var.__location}\n"
else
node_error @var, "Undefined variable"
return val_reg
Int: (ast)=>
t = get_type(ast, true)
s = ast[0]\gsub("_","")
n = if s\match("^0x")
tonumber(s\sub(3), 16)
else
tonumber(s)
if t\is_a(Types.Num)
return "d_#{n}"
elseif t\is_a(Types.Num32)
return "s_#{n}"
min,max = -(2^(t.bytes*8-1)), 2^(t.bytes*8-1)-2
if n == t.nil_value
node_error ast, "This value is reserved for represeting `nil` and can't be used as an integer. Consider using a larger integer type."
elseif n > max
node_error ast, "This value is too large to fit into a #{t.bytes}-byte signed integer (max value: #{math.floor(max)})"
elseif n < min
node_error ast, "This value is too small to fit into a #{t.bytes}-byte signed integer (min value: #{math.floor(min)})"
return "#{n}"
Float: (ast)=>
s = ast[0]\gsub("_","")
t = get_type(ast, true)
prefix = t\is_a(Types.Num) and "d" or "s"
return "#{prefix}_#{tonumber(s)}"
Measure: (ast)=>
n = tonumber((ast.amount[0]\gsub("_","")))
m = Measure(n, ast.units[0]\gsub("[<>]",""))
return "d_#{m.amount}"
Percent: (ast)=>
s = ast[0]\gsub("_","")\gsub("%%","")
return "d_#{tonumber(s)/100.0}"
Nil: (ast)=>
child = ast
parent = ast.__parent
while parent
if not parent.__tag
parent = parent.__parent
continue
if parent.__tag == "Return"
while parent and not (parent.__tag == "FnDecl" or parent.__tag == "Lambda")
parent,child = parent.__parent,parent
continue
if parent.__tag == "Equal" or parent.__tag == "NotEqual"
other = (child == parent.lhs) and parent.rhs or parent.lhs
t = get_type(other, true)
if t\is_a(Types.OptionalType) and t != Types.NilType
t = t.nonnil
-- if t.base_type == "s" or t.base_type == "d"
-- nil_reg = @fresh_local "nil"
-- return nil_reg, "#{nil_reg} =#{t.base_type} cast #{t.nil_value}\n"
-- else
return "#{t.nil_value}"
t = if parent.__tag == "Declaration"
get_type parent.value, true
elseif parent.__tag == "Assignment"
t = nil
for i,rhs in ipairs parent.rhs
if rhs == child
t = get_type parent.lhs[i], true
break
t
elseif parent.__tag == "StructField"
field = parent
while parent.__tag != "Struct" and parent.__tag != "Union"
parent = parent.__parent
struct_type = get_type parent, true
if field.name
struct_type.members[field.name].type
else
field_type = nil
for i,f in ipairs field.__parent
if f == field
field_type = node_assert(struct_type.members[i] or struct_type.sorted_members[i], ast, "Not a #{struct_type} field").type
break
field_type
elseif parent.__tag == "List"
get_type(parent, true).item_type
elseif parent.__tag == "TableEntry"
entry = parent
tab = parent.__parent
while tab.__tag != "Table"
tab = tab.__parent
table_type = get_type(tab, true)
child == entry.key and table_type.key_type or table_type.value_type
elseif parent.__tag == "FnDecl" or parent.__tag == "Lambda"
break
elseif parent.__tag == "Clause"
parent,child = parent.__parent,parent
continue
else
get_type(parent, true)
if t != Types.NilType and t\is_a(Types.OptionalType)
-- if t.base_type == "s" or t.base_type == "d"
-- nil_reg = @fresh_local "nil"
-- return nil_reg, "#{nil_reg} =#{t.base_type} cast #{t.nil_value}\n"
-- else
return "#{t.nil_value}"
elseif parent.__tag == "Declaration"
return "0"
parent,child = parent.__parent,parent
return "0"
Bool: (ast)=> (ast[0] == "yes" and "1" or "0")
Cast: (ast)=>
reg = @add_value ast.expr
cast_type = parse_type ast.type
actual_type = get_type(ast.expr, true)
if not actual_type or (actual_type and cast_type.base_type == actual_type.base_type)
return reg
c = @fresh_local "casted"
abt = actual_type.base_type
cbt = cast_type.base_type
if actual_type\is_numeric! and cast_type\is_numeric!
if (abt == "s" or abt == "d") and not (cbt == "s" or cbt == "d")
@add "#{c} =#{cbt} #{abt}tosi #{reg}\n"
return c
elseif (cbt == "s" or cbt == "d") and not (abt == "s" or abt == "d")
@add "#{c} =#{cbt} s#{abt}tof #{reg}\n"
return c
if abt == "l" and cbt == "w"
@add "#{c} =w copy #{reg}\n"
elseif abt == "w" and cbt == "l"
@add "#{c} =l extsw #{reg}\n"
elseif abt == "s" and cbt == "d"
@add "#{c} =d exts #{reg}\n"
elseif abt == "d" and cbt == "s"
@add "#{c} =#{cast_type.base_type} truncd #{reg}\n"
else
@add "#{c} =#{cbt} cast #{reg}\n"
return c
As: (ast)=>
src_type, dest_type = ast.expr.__type, ast.__type
fndec = node_assert ast.__converter, ast, "Couldn't figure out how to convert #{src_type} into #{dest_type}"
fn_reg = fndec.__register
reg = @add_value ast.expr
converted = @fresh_local "converted"
@add "#{converted} =#{dest_type.base_type} call #{fn_reg}(#{src_type.base_type} #{reg})\n"
return converted
TypeOf: (ast)=>
return @get_string_reg("#{get_type(ast.expression)\verbose_type!}", "typename")
SizeOf: (ast)=>
t = get_type(ast.expression, true)
return "#{t.bytes}"
String: (ast)=>
str = @fresh_local "str"
if (#ast.content == 0) or (#ast.content == 1 and type(ast.content[1]) == "string")
@add "#{str} =l call $bl_string(l #{@get_string_reg(ast.content[0])})\n"
return str
chunks = {}
i = ast.content.start
for interp in *ast.content
if interp.start > i
chunk = ast.content[0]\sub(1+(i-ast.content.start), interp.start-ast.content.start)
table.insert chunks, chunk unless chunk == ""
table.insert chunks, interp
i = interp.after
if ast.content.after > i
chunk = ast.content[0]\sub(1+(i-ast.content.start), ast.content.after-ast.content.start)
table.insert chunks, chunk unless chunk == ""
cord_reg = @fresh_local "cord.buf"
@add "#{cord_reg} =l copy 0\n"
chunk_reg = @fresh_local "string.chunk"
for i,chunk in ipairs chunks
if type(chunk) == 'string'
@add "#{chunk_reg} =l copy #{@get_string_reg chunk, "str.literal"}\n"
else
t = get_type(chunk)
node_assert t, chunk, "Couldn't get this type"
val_reg = @add_value chunk
if t == Types.String
@add "#{chunk_reg} =l copy #{val_reg}\n"
elseif chunk.__converter
@add "#{chunk_reg} =l call #{chunk.__converter.__register}(#{t.base_type} #{val_reg})\n"
else
fn = @get_tostring_fn t, ast
@add "#{chunk_reg} =l call #{fn}(#{t.base_type} #{val_reg}, l 0)\n"
@add "#{cord_reg} =l call $CORD_cat(l #{cord_reg}, l #{chunk_reg})\n"
@add "#{cord_reg} =l call $CORD_to_const_char_star(l #{cord_reg})\n"
@add "#{str} =l call $bl_string(l #{cord_reg})\n"
return str
DSL: (ast)=>
str = @fresh_local "str"
if (#ast.content == 0) or (#ast.content == 1 and type(ast.content[1]) == "string")
@add "#{str} =l call $bl_string(l #{@get_string_reg(ast.content[0])})\n"
return str
@add "#{str} =l call $bl_string(l #{@get_string_reg("", "emptystr")})\n"
dsl_type = get_type(ast, true)
stringify = (val)->
t = get_type(val, true)
val_reg = @add_value val
safe = if t == dsl_type or val.__tag == "Escape" or val.__tag == "Newline"
val_reg
else
converter = node_assert val.__converter, val, "Couldn't figure out how to convert #{val.value.__type} to #{dsl_type}"
escaped = @fresh_local "escaped"
@add "#{escaped} =l call #{converter.__register}(#{t.base_type} #{val_reg})\n"
escaped
@add "#{str} =l call $bl_string_append_string(l #{str}, l #{safe})\n"
i = ast.content.start
for interp in *ast.content
if interp.start > i
chunk = ast.content[0]\sub(1+(i-ast.content.start), interp.start-ast.content.start)
@add "#{str} =l call $bl_string_append_string(l #{str}, l #{@get_string_reg chunk})\n"
stringify(interp)
i = interp.after
if ast.content.after > i
chunk = ast.content[0]\sub(1+(i-ast.content.start), ast.content.after-ast.content.start)
@add "#{str} =l call $bl_string_append_string(l #{str}, l #{@get_string_reg chunk})\n"
return str
FieldName: (ast)=>
name = @fresh_local ast[0]
@add "#{name} =l call $bl_string(l #{@get_string_reg ast[0], ast[0]})\n"
return name
Interp: (ast)=> @add_value ast.value
Newline: (ast)=> @get_string_reg("\n", "newline")
Escape: (ast)=>
esc = {a:'\a',b:'\b',t:'\t',n:'\n',r:'\r',v:'\v'}
text = ast[0]\sub(2)
c = if esc[text]
esc[text]\byte(1)
elseif text\match('[0-8][0-8][0-8]')
tonumber(text, 8)
elseif text\match('x[0-9a-fA-F][0-9a-fA-F]')
tonumber(text\sub(2), 16)
else
text\byte(1)
return @get_string_reg(("%c")\format(c), "char")
Negative: (ast)=>
t = get_type ast.value
if t\is_numeric!
reg = @add_value ast.value
ret = @fresh_local "neg"
@add "#{ret} =#{t.base_type} neg #{reg}\n"
return ret
elseif t\is_a(Types.Range)
orig = @add_value ast.value
range = @fresh_local "neg.range"
@add "#{range} =l call $range_backwards(l #{orig})\n"
return range
else
node_error ast, "Invalid type to negate: #{t}"
Len: (ast)=>
t = get_type ast.value
value = @add_value ast.value
len = @fresh_local "len"
if t\is_a(Types.Range)
@add "#{len} =l call $range_len(l #{value})\n"
elseif t\is_a(Types.ListType)
@add "#{len} =l loadl #{value}\n"
elseif t\is_a(Types.TableType)
@add "#{len} =l call $hashmap_length(l #{value})\n"
elseif t\is_a(Types.String)
@add "#{len} =l call $strlen(l #{value})\n"
else
node_error ast, "Expected List or Range type, not #{t}"
return len
Not: (ast)=>
t = get_type(ast.value)
b = @add_value ast.value
ret = @fresh_local "not"
@add "#{ret} =w cne#{t.base_type} #{b}, 1\n"
return ret
IndexedTerm: (ast)=>
t = get_type ast.value, true
if t\is_a(Types.TypeValue) and t.type\is_a(Types.EnumType) and ast.index.__tag == "FieldName"
value = node_assert t.type.field_values[ast.index[0]], ast, "Couldn't find enum field: .#{ast.index[0]} on type #{t.type}"
return "#{value}"
elseif t\is_a(Types.TypeValue) and (t.type\is_a(Types.StructType) or t.type\is_a(Types.UnionType)) and ast.index.__tag == "FieldName"
if method = ast.__staticmethod
node_assert method.__register, method, "No register found"
return method.__register
else
loc = ast.__declaration.__location
tmp = @fresh_local "#{ast[0]}.value"
@add "#{tmp} =#{t.base_type} #{t.load} #{loc}\n"
return tmp
if t\is_a(Types.ListType)
item_type = t.item_type
index_type = get_type(ast.index)
ListMethods = require 'list_methods'
if index_type\is_a(Types.Int)
return ListMethods.methods.get_or_fail(ast, @)
elseif index_type\is_a(Types.Range)
return ListMethods.methods.range(ast, @)
elseif ast.index.__tag == "FieldName"
node_error ast, "Field access on lists is not currently supported"
else
node_error ast.index, "Index is #{index_type} instead of Int or Range"
elseif t\is_a(Types.TableType)
TableMethods = require "table_methods"
return TableMethods.methods.get_or_fail(ast, @)
elseif t\is_a(Types.StructType)
if ast.__method
return assert(ast.__method.__register)
member = if ast.index.__tag == "FieldName"
member_name = ast.index[0]
node_assert t.members[member_name], ast.index, "Not a valid struct member of #{t}"
t.members[member_name]
elseif ast.index.__tag == "Int"
i = tonumber(ast.index[0])
node_assert 1 <= i and i <= #t.members, ast.index, "#{t} only has members between 1 and #{#t.members}"
t.members[i]
else
node_error ast.index, "Structs can only be indexed by a field name or Int literal"
struct_reg = @add_value ast.value
ret = @fresh_local "member"
if member.inline
@add "#{ret} =l add #{struct_reg}, #{member.offset}\n"
else
loc = @fresh_local "member.loc"
@add "#{loc} =l add #{struct_reg}, #{member.offset}\n"
@add "#{ret} =#{member.type.base_type} #{member.type.load} #{loc}\n"
return ret
elseif t\is_a(Types.UnionType)
if ast.__method
return assert(ast.__method.__register)
node_assert ast.index.__tag == "FieldName", ast, "Not a valid union field name"
member_name = ast.index[0]
member = node_assert t.members[member_name], ast.index, "Not a valid union member of #{t\verbose_type!}"
union_reg = @add_value ast.value
is_member,tag,ret = @fresh_locals "is.member","tag","ret"
@add "#{tag} =l loadl #{union_reg}\n"
@add "#{is_member} =w ceql #{tag}, #{member.index}\n"
if_tag,use_nil,done = @fresh_labels "if.tag","use.nil","done"
@add "jnz #{is_member}, #{if_tag}, #{use_nil}\n"
@add_label if_tag
value_loc = @fresh_local "value.loc"
@add "#{value_loc} =l add #{union_reg}, 8\n"
@add "#{ret} =#{member.type.base_type} #{member.type.load} #{value_loc}\n"
@add "jmp #{done}\n"
@add_label use_nil
@set_nil member.type, ret
@add "jmp #{done}\n"
@add_label done
return ret
elseif t\is_a(Types.Range)
index_type = get_type(ast.index)
-- TODO: Slice ranges
node_assert index_type\is_a(Types.Int) or index_type == Types.OptionalType(Types.Int), ast.index, "Index is #{index_type} instead of Int"
range_reg, index_reg = @add_values ast.value, ast.index
ret = @fresh_local "range.nth"
@add "#{ret} =l call $range_nth(l #{range_reg}, l #{index_reg})\n"
return ret
elseif t\is_a(Types.String)
index_type = get_type(ast.index)
str, index_reg = @add_values ast.value, ast.index
if index_type\is_a(Types.Int) or index_type == Types.OptionalType(Types.Int) -- Get nth character as an Int
char = @fresh_local "char"
@add "#{char} =l call $bl_string_nth_char(l #{str}, l #{index_reg})\n"
return char
elseif index_type\is_a(Types.Range) -- Get a slice of the string
slice = @fresh_local "slice"
@add "#{slice} =l call $bl_string_slice(l #{str}, l #{index_reg})\n"
return slice
else
node_error ast.index, "Index is #{index_type} instead of Int or Range"
else
node_error ast.value, "Indexing is not valid on type #{t\verbose_type!}"
List: (ast)=>
list,list_items,size,p = @fresh_locals "list", "list.items", "list.size", "p"
@add "#{list} =l call $gc_alloc(l 17)\n" -- size of list struct: {size, items, slack}
if #ast == 0
return list
@add "#{size} =l copy 0\n"
@add "#{list_items} =l copy 0\n"
item_type = get_type(ast).item_type
get_add_item_code = (item)->
code = @new_code!
item_reg = code\add_value item
code\add "#{size} =l add #{size}, #{item_type.bytes}\n"
code\add "#{list_items} =l call $gc_realloc(l #{list_items}, l #{size})\n"
code\add "#{p} =l add #{list_items}, #{size}\n"
code\add "#{p} =l sub #{p}, #{item_type.bytes}\n"
code\add "#{item_type.store} #{item_reg}, #{p}\n"
return code
next_label = nil
for i,val in ipairs ast
if next_label
@add "jmp #{next_label}\n" unless @ends_with_jump!
@add_label next_label
next_label = nil
switch val.__tag
when "If"
true_label = @fresh_label "if.true"
cond, expr = assert(val[1].condition), assert(val[1].body[1])
cond_reg = @add_value cond
next_label = @fresh_label "list.next"
@check_truthiness get_type(cond), cond_reg, true_label, next_label
@add_label true_label
@add get_add_item_code(expr)
when "For"
iter_reg = @add_value val.iterable
key_reg, value_reg = if val.index and val.val
val.index.__register, val.val.__register
elseif val.val and val.iterable.__type\works_like_a(Types.TableType)
val.val.__register, nil
elseif val.val
nil, val.val.__register
else
nil, nil
@add_for_loop {
type: val.iterable.__type, :iter_reg, :key_reg, :value_reg, scope:{val.body, val.filter},
make_body: (-> get_add_item_code(val.body[1])), filter: val.filter,
}
when "While"
@add_while_loop val, (-> get_add_item_code(val.body[1]))
when "Repeat"
@add_repeat_loop val, (-> get_add_item_code(val.body[1]))
else
@add get_add_item_code(val)
if next_label
@add "jmp #{next_label}\n" unless @ends_with_jump!
@add_label next_label
@add "#{size} =l div #{size}, #{item_type.bytes}\n"
@add "storel #{size}, #{list}\n"
items_loc = @fresh_local "items.loc"
@add "#{items_loc} =l add #{list}, 8\n"
@add "storel #{list_items}, #{items_loc}\n"
-- List slack is zero, no need to set
return list
Table: (ast)=>
t = get_type ast
node_assert not t.key_type\is_a(Types.OptionalType) and not t.value_type\is_a(Types.OptionalType), ast,
"Nil cannot be stored in a table, but this table is #{t}"
tab = @fresh_local "table"
@add "#{tab} =l call $hashmap_new(l 0)\n"
if #ast == 0
return tab
TableMethods = require "table_methods"
add_entry = (entry)->
code = @new_code!
TableMethods.methods.set({tab,entry.key,entry.value}, code)
return code
next_label = nil
for i,val in ipairs ast
if next_label
@add "jmp #{next_label}\n" unless @ends_with_jump!
@add "#{next_label}\n"
next_label = nil
switch val.__tag
when "If"
true_label = @fresh_label "if.true"
cond, expr = assert(val[1].condition), assert(val[1].body[1])
cond_reg = @add_value cond
next_label = @fresh_label "list.next"
@check_truthiness get_type(cond), cond_reg, true_label, next_label
@add_label true_label
@add add_entry expr
when "For"
iter_reg = @add_value val.iterable
key_reg, value_reg = if val.index and val.val
val.index.__register, val.val.__register
elseif val.val and val.iterable.__type\works_like_a(Types.TableType)
val.val.__register, nil
elseif val.val
nil, val.val.__register
else
nil, nil
@add_for_loop {
type: val.iterable.__type, :iter_reg, :key_reg, :value_reg, scope:{val.body, val.filter},
make_body: (-> add_entry(val.body[1])), filter: val.filter,
}
when "While"
@add_while_loop val, (-> add_entry(val.body[1]))
when "Repeat"
@add_repeat_loop val, (-> add_entry(val.body[1]))
else
@add add_entry(val)
if next_label
@add "jmp #{next_label}\n" unless @ends_with_jump!
@add_label next_label
return tab
Range: (ast)=>
range = @fresh_local "range"
if ast.first and ast.next and ast.last
first_reg,next_reg,last_reg = @add_values ast.first, ast.next, ast.last
@add "#{range} =l call $range_new(l #{first_reg}, l #{next_reg}, l #{last_reg})\n"
elseif ast.first and ast.next and not ast.last
first_reg,next_reg = @add_values ast.first, ast.next
@add "#{range} =l call $range_new_first_next(l #{first_reg}, l #{next_reg})\n"
elseif ast.first and not ast.next and ast.last
first_reg,last_reg = @add_values ast.first, ast.last
@add "#{range} =l call $range_new_first_last(l #{first_reg}, l #{last_reg})\n"
elseif ast.first and not ast.next and not ast.last
first_reg = @add_values ast.first
@add "#{range} =l call $range_new_first_last(l #{first_reg}, l 999999999999999999)\n"
elseif not ast.first and not ast.next and ast.last
last_reg = @add_values ast.last
@add "#{range} =l call $range_new_first_last(l -999999999999999999, l #{last_reg})\n"
elseif not ast.first and not ast.next and not ast.last
@add "#{range} =l call $range_new_first_last(l -999999999999999999, l 999999999999999999)\n"
else
node_error ast, "WTF"
return range
Or: (ast)=>
done_label = @fresh_label "or.end"
code = ""
t = get_type(ast)
ret_reg = @fresh_local "any.true"
for i,val in ipairs ast
val_reg = @add_value val
false_label = @fresh_label "or.falsey"
@add "#{ret_reg} =#{t.base_type} copy #{val_reg}\n"
if i < #ast
@check_truthiness get_type(val), val_reg, done_label, false_label
@add_label false_label
@add "#{done_label}\n"
return ret_reg
And: (ast)=>
done_label = @fresh_label "and.end"
t = get_type(ast)
ret_reg = @fresh_local "all.true"
for i,val in ipairs ast
val_reg = @add_value val
true_label = @fresh_label "and.truthy"
@add "#{ret_reg} =#{t.base_type} copy #{val_reg}\n"
if i < #ast
@check_truthiness get_type(val), val_reg, true_label, done_label
@add_label true_label
@add_label done_label
return ret_reg
Xor: (ast)=>
for val in *ast
node_assert get_type(val)\is_a(Types.Bool), val, "Expected Bool here, but got #{get_type val}"
return @add_infix_op ast, "xor"
AddSub: (ast)=>
t_lhs,t_rhs = get_type(ast.lhs),get_type(ast.rhs)
tl_nn, tr_nn = (t_lhs.nonnil or t_lhs), (t_rhs.nonnil or t_rhs)
if ast.op[0] == "+"
if tl_nn == tr_nn and (tl_nn\is_numeric! or tl_nn\is_a(Types.MeasureType))
return @add_infix_op ast, "add"
elseif t_lhs == t_rhs and t_lhs\works_like_a(Types.String)
return @add_infix_op ast, (ret,lhs,rhs)->
"#{ret} =l call $bl_string_append_string(l #{lhs}, l #{rhs})\n"
elseif t_lhs\works_like_a(Types.ListType) and t_rhs\is_a(t_lhs.item_type)
return @add_infix_op ast, (ret,lhs,rhs)->
list_reg,item_reg,item_type = lhs,rhs,t_rhs
new_len,len,new_items,items,new_size,size,tmp = @fresh_locals "new.len","len","new.items","items","new.size","size","tmp"
code = @new_code "\n# Append\n"
code\add "#{len} =l loadl #{list_reg}\n"
code\add "#{size} =l mul #{len}, #{item_type.bytes}\n"
code\add "#{new_len} =l add #{len}, 1\n"
code\add "#{tmp} =l add #{list_reg}, 8\n"
code\add "#{items} =l loadl #{tmp}\n"
code\add "#{new_size} =l mul #{new_len}, #{item_type.bytes}\n"
code\add "#{new_items} =l call $gc_alloc(l #{new_size})\n"
code\add "#{tmp} =l call $mempcpy(l #{new_items}, l #{items}, l #{size})\n"
code\add "#{item_type.store} #{item_reg}, #{tmp}\n"
code\add "#{ret} =l call $gc_alloc(l 16)\n"
code\add "storel #{new_len}, #{ret}\n"
code\add "#{tmp} =l add #{ret}, 8\n"
code\add "storel #{new_items}, #{tmp}\n"
code\add "\n"
return code
elseif t_rhs\works_like_a(Types.ListType) and t_lhs\is_a(t_rhs.item_type)
return @add_infix_op ast, (ret,lhs,rhs)->
list_reg,item_reg,item_type = rhs,lhs,t_lhs
new_len,len,new_items,items,new_size,size,tmp = @fresh_locals "new.len","len","new.items","items","new.size","size","tmp"
code = @new_code "\n# Prepend\n"
code\add "#{len} =l loadl #{list_reg}\n"
code\add "#{size} =l mul #{len}, #{item_type.bytes}\n"
code\add "#{new_len} =l add #{len}, 1\n"
code\add "#{tmp} =l add #{list_reg}, 8\n"
code\add "#{items} =l loadl #{tmp}\n"
code\add "#{new_size} =l mul #{new_len}, #{item_type.bytes}\n"
code\add "#{new_items} =l call $gc_alloc(l #{new_size})\n"
code\add "#{item_type.store} #{item_reg}, #{new_items}\n"
code\add "#{tmp} =l add #{new_items}, #{item_type.bytes}\n"
code\add "call $memcpy(l #{tmp}, l #{items}, l #{size})\n"
code\add "#{ret} =l call $gc_alloc(l 16)\n"
code\add "storel #{new_len}, #{ret}\n"
code\add "#{tmp} =l add #{ret}, 8\n"
code\add "storel #{new_items}, #{tmp}\n"
code\add "\n"
return code
elseif t_lhs == t_rhs and t_lhs\works_like_a(Types.ListType)
return @add_infix_op ast, (ret,lhs,rhs)->
len1,len2,len3,items1,items2,items3,size,tmp = @fresh_locals "len1","len2","len3","items1","items2","items3","size","tmp"
code = @new_code "#{len1} =l loadl #{lhs}\n"
code\add "#{len2} =l loadl #{rhs}\n"
code\add "#{lhs} =l add #{lhs}, 8\n"
code\add "#{items1} =l loadl #{lhs}\n"
code\add "#{rhs} =l add #{rhs}, 8\n"
code\add "#{items2} =l loadl #{rhs}\n"
code\add "#{len3} =l add #{len1}, #{len2}\n"
code\add "#{size} =l mul #{len3}, #{t_lhs.item_type.bytes}\n"
code\add "#{ret} =l call $gc_alloc(l 16)\n"
code\add "storel #{len3}, #{ret}\n"
code\add "#{items3} =l call $gc_alloc(l #{size})\n"
code\add "#{tmp} =l add #{ret}, 8\n"
code\add "storel #{items3}, #{tmp}\n"
code\add "#{size} =l mul #{len1}, #{t_lhs.item_type.bytes}\n"
code\add "#{items3} =l call $mempcpy(l #{items3}, l #{items1}, l #{size})\n"
code\add "#{size} =l mul #{len2}, #{t_lhs.item_type.bytes}\n"
code\add "call $memcpy(l #{items3}, l #{items2}, l #{size})\n"
return code
else
node_error ast, "Addition is not supported for #{t_lhs} and #{t_rhs}"
else -- "-"
if tl_nn == tr_nn and (tl_nn\is_numeric! or tl_nn\is_a(Types.MeasureType))
return @add_infix_op ast, "sub"
else
node_error ast, "Subtraction is not supported for #{t_lhs} and #{t_rhs}"
MulDiv: (ast)=>
t_lhs,t_rhs = get_type(ast.lhs),get_type(ast.rhs)
tl_nn, tr_nn = (t_lhs.nonnil or t_lhs), (t_rhs.nonnil or t_rhs)
if ast.op[0] == "*"
if tl_nn == tr_nn and tl_nn\is_numeric!
return @add_infix_op ast, "mul"
elseif (tl_nn\is_a(Types.MeasureType) and tr_nn\is_a(Types.Num)) or (tl_nn\is_a(Types.Num) and tr_nn\is_a(Types.MeasureType)) or (tl_nn\is_a(Types.MeasureType) and tr_nn\is_a(Types.MeasureType))
return @add_infix_op ast, "mul"
else
node_error ast, "Multiplication is not supported for #{t_lhs} and #{t_rhs}"
else -- "/"
if tl_nn == tr_nn and tl_nn\is_numeric!
return @add_infix_op ast, "div"
elseif (tl_nn\is_a(Types.MeasureType) and tr_nn\is_a(Types.Num)) or (tl_nn\is_a(Types.Num) and tr_nn\is_a(Types.MeasureType)) or (tl_nn\is_a(Types.MeasureType) and tr_nn\is_a(Types.MeasureType))
return @add_infix_op ast, "div"
else
node_error ast, "Division is not supported for #{t_lhs} and #{t_rhs}"
AddUpdate: (ast)=>
lhs_type,rhs_type = get_type(ast.lhs),get_type(ast.rhs)
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
if nonnil_eq(lhs_type, rhs_type) and lhs_type\is_numeric!
@add "#{lhs_reg} =#{lhs_type.base_type} add #{lhs_reg}, #{rhs_reg}\n"
elseif lhs_type == rhs_type and lhs_type\works_like_a(Types.String)
@add "#{lhs_reg} =l call $bl_string_append_string(l #{lhs_reg}, l #{rhs_reg})\n"
elseif lhs_type\works_like_a(Types.ListType) and rhs_type\is_a(lhs_type.item_type)
node_error ast, "Cannot use += with Lists, use list.append(other) or list = list+other instead"
elseif lhs_type == rhs_type and lhs_type\works_like_a(Types.ListType)
node_error ast, "Cannot use += with Lists, use list.append_all(other) or list = list+other instead"
else
node_error ast, "Addition is not supported for #{lhs_type} and #{rhs_type}"
if ast.lhs.__location
@add "#{lhs_type.store} #{lhs_reg}, #{ast.lhs.__location}\n"
return lhs_reg
SubUpdate: (ast)=>
lhs_type,rhs_type = get_type(ast.lhs),get_type(ast.rhs)
unless nonnil_eq(lhs_type, rhs_type) and lhs_type\is_numeric!
node_error ast, "Subtraction is not supported for #{lhs_type} and #{rhs_type}"
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
@add "#{lhs_reg} =#{lhs_type.base_type} sub #{lhs_reg}, #{rhs_reg}\n"
if ast.lhs.__location
@add "#{lhs_type.store} #{lhs_reg}, #{ast.lhs.__location}\n"
return lhs_reg
MulUpdate: (ast)=>
lhs_type,rhs_type = get_type(ast.lhs),get_type(ast.rhs)
unless nonnil_eq(lhs_type, rhs_type) and lhs_type\is_numeric!
node_error ast, "Multiplication is not supported for #{lhs_type} and #{rhs_type}"
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
@add "#{lhs_reg} =#{lhs_type.base_type} mul #{lhs_reg}, #{rhs_reg}\n"
if ast.lhs.__location
@add "#{lhs_type.store} #{lhs_reg}, #{ast.lhs.__location}\n"
return lhs_reg
DivUpdate: (ast)=>
lhs_type,rhs_type = get_type(ast.lhs),get_type(ast.rhs)
unless nonnil_eq(lhs_type, rhs_type) and lhs_type\is_numeric!
node_error ast, "Division is not supported for #{lhs_type} and #{rhs_type}"
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
@add "#{lhs_reg} =#{lhs_type.base_type} div #{lhs_reg}, #{rhs_reg}\n"
if ast.lhs.__location
@add "#{lhs_type.store} #{lhs_reg}, #{ast.lhs.__location}\n"
return lhs_reg
OrUpdate: (ast)=>
t_lhs, t_rhs = get_type(ast.lhs), get_type(ast.rhs)
true_label,false_label = @fresh_labels "or.lhs.true", "or.lhs.false"
lhs_reg = @add_value ast.lhs
@check_truthiness t_lhs, lhs_reg, true_label, false_label
@add_label false_label
rhs_reg = @add_value ast.rhs
@add "#{lhs_reg} =#{t_lhs.base_type} copy #{rhs_reg}\n"
@add "#{t_lhs.store} #{lhs_reg}, #{ast.lhs.__location}\n" if ast.lhs.__location
@add "jmp #{true_label}\n"
@add_label true_label
return lhs_reg
AndUpdate: (ast)=>
t_lhs, t_rhs = get_type(ast.lhs), get_type(ast.rhs)
true_label,false_label = @fresh_labels "and.lhs.true", "and.lhs.false"
lhs_reg = @add_value ast.lhs
@check_truthiness t_lhs, lhs_reg, true_label, false_label
@add_label true_label
rhs_reg = @add_value ast.rhs
@add "#{lhs_reg} =#{t_lhs.base_type} copy #{rhs_reg}\n"
@add "#{t_lhs.store} #{lhs_reg}, #{ast.lhs.__location}\n" if ast.lhs.__location
@add "jmp #{false_label}\n"
@add_label false_label
return lhs_reg
ButWithUpdate: (ast)=>
t = get_type ast.base
node_assert t\is_a(Types.StructType), ast, "| operator is only supported for Struct types"
base_reg = @add_value ast.base
ret = @fresh_local "#{t.name\lower!}.butwith"
@add "#{ret} =l call $gc_alloc(l #{t.memory_size})\n"
@add "call $memcpy(l #{ret}, l #{base_reg}, l #{t.memory_size})\n"
p = @fresh_local "#{t.name\lower!}.butwith.member.loc"
for override in *ast
memb = if override.index
t.members[tonumber(override.index[0])]
elseif override.field
t.members[override.field[0]]
else
node_error override, "I don't know what this is"
node_assert memb, override, "Not a valid member of #{t}"
node_assert get_type(override.value)\is_a(memb.type), override.value, "Not a #{memb}"
val_reg = @add_value override.value
@add "#{p} =l add #{ret}, #{memb.offset}\n"
@add "#{memb.type.store} #{val_reg}, #{p}\n"
@add "#{t.store} #{base_reg}, #{ast.base.__location}\n" if ast.base.__location
return "#{base_reg}"
MethodCallUpdate: (ast)=>
dest = ast[1]
node_assert dest and dest.__tag == "Var", dest, "Method call updates expect a variable"
update_sig = "(#{concat [tostring(get_type(a)) for a in *ast], ","})=>#{get_type(dest)}"
fn_type = get_type ast.fn
fn_reg = @add_value ast.fn
if fn_type
node_assert fn_type\is_a(Types.FnType), ast.fn, "This is not a function, it's a #{fn_type or "???"}"
node_assert "#{fn_type}" == update_sig, ast, "For this to work, #{ast.fn[0]} should be #{update_sig}, not #{fn_type}"
args = {}
for arg in *ast
arg_reg = @add_value arg
table.insert args, "#{get_type(arg).base_type} #{arg_reg}"
ret_type = fn_type and fn_type.return_type or get_type(dest)
if dest.__register
@add "#{dest.__register} =#{ret_type.base_type} call #{fn_reg}(#{concat args, ", "})\n"
return dest.__register
elseif dest.__location
ret = @fresh_local "return"
@add "#{ret} =#{ret_type.base_type} call #{fn_reg}(#{concat args, ", "})\n"
@add "#{ret_type.store} #{ret}, #{dest.__location}\n"
return ret
Mod: (ast)=>
t = get_type(ast)
if (t.nonnil or t)\is_a(Types.Int) or (t.nonnil or t)\is_a(Types.Num)
lhs_reg, rhs_reg = @add_values ast.lhs, ast.rhs
ret = @fresh_local "remainder"
if t\is_a(Types.Int)
@add "#{ret} =l call $sane_lmod(l #{lhs_reg}, l #{rhs_reg})\n"
else
@add "#{ret} =d call $sane_fmod(d #{lhs_reg}, d #{rhs_reg})\n"
return ret
else
node_error ast, "Modulus is not supported for #{get_type(ast.lhs)} and #{get_type(ast.rhs)}"
Pow: (ast)=>
base_type = get_type ast.base
exponent_type = get_type ast.exponent
base_reg, exponent_reg = @add_values ast.base, ast.exponent
ret_reg = @fresh_local "result"
if base_type == exponent_type and base_type\is_a(Types.Int)
@add "#{ret_reg} =l call $ipow(l #{base_reg}, l #{exponent_reg})\n"
elseif base_type == exponent_type and base_type\is_a(Types.Num)
@add "#{ret_reg} =d call $pow(d #{base_reg}, d #{exponent_reg})\n"
else
node_error ast, "Exponentiation is not supported for #{base_type} and #{exponent_type}"
return ret_reg
ButWith: (ast)=>
t = get_type ast.base
if t\is_a(Types.ListType)
error "Not impl"
elseif t\is_a(Types.String)
error "Not impl"
elseif t\is_a(Types.StructType)
lhs_reg = @add_value ast.base
ret = @fresh_local "#{t.name\lower!}.butwith"
struct_size = 8*#t.sorted_members
@add "#{ret} =l call $gc_alloc(l #{struct_size})\n"
@add "call $memcpy(l #{ret}, l #{lhs_reg}, l #{struct_size})\n"
p = @fresh_local "#{t.name\lower!}.butwith.member.loc"
for override in *ast
member = if override.index
t.members[tonumber(override.index[0])]
elseif override.field
t.members[override.field[0]]
else
node_error override, "I don't know what this is"
override_type = get_type(override.value, true)
node_assert override_type\is_a(member.type), override.value, "Not a #{member.type}"
val_reg = @add_value override.value
@add "#{p} =l add #{ret}, #{member.offset}\n"
@add "#{member.type.store} #{val_reg}, #{p}\n"
return ret
else
node_error ast, "| operator is only supported for List and Struct types"
In: (ast)=>
haystack_type = get_type(ast.haystack, true)
needle_type = get_type(ast.needle, true)
if haystack_type\is_a(Types.ListType) and needle_type\is_a(haystack_type.item_type)
found = @fresh_locals "found"
done = @fresh_label "in.done"
haystack_reg,needle_reg = @add_values ast.haystack, ast.needle
@add "#{found} =w copy 0\n"
item_reg = @fresh_local("item")
@add_for_loop {
type: haystack_type, iter_reg: haystack_reg, value_reg:item_reg
make_body: ->
base_type = haystack_type.item_type.base_type
code = if needle_type == Types.NilType and (base_type == 's' or base_type == 'd')
@new_code "#{found} =w cuo#{base_type} #{item_reg}, #{base_type}_0.0\n"
else
@new_code "#{found} =w ceq#{base_type} #{item_reg}, #{needle_reg}\n"
keep_going = @fresh_label "keep_going"
@add "jnz #{found}, #{done}, #{keep_going}\n"
@add "#{keep_going}\n"
return code
}
@add "#{done}\n"
return found
elseif haystack_type\is_a(Types.TableType) and needle_type\is_a(haystack_type.key_type)
needle_reg,haystack_reg = @add_values ast.needle, ast.haystack
key_getter = @fresh_local "key.getter"
TableMethods = require 'table_methods'
@add TableMethods.to_table_format @, haystack_type.key_type, needle_reg, key_getter
found = @fresh_local "found"
@add "#{found} =l call $hashmap_get(l #{haystack_reg}, l #{key_getter})\n"
@add "#{found} =l cnel #{found}, 0\n"
return found
elseif haystack_type == needle_type and haystack_type\is_a(Types.String)
needle_reg,haystack_reg = @add_values ast.needle, ast.haystack
found = @fresh_local "found"
@add "#{found} =l call $strstr(l #{haystack_reg}, l #{needle_reg})\n"
@add "#{found} =l cnel #{found}, 0\n"
return found
else
-- TODO: support `Int in Range`, `[Foo] in [Foo]`, `Range in Range` etc.
node_error ast, "Checking if #{needle_type} is in #{haystack_type} is not supported"
Less: (ast)=>
t = get_type(ast.lhs)
sign = (t.base_type == 's' or t.base_type == 'd') and "" or "s"
return @add_comparison ast, "c#{sign}lt#{t.base_type}"
LessEq: (ast)=>
t = get_type(ast.lhs)
sign = (t.base_type == 's' or t.base_type == 'd') and "" or "s"
return @add_comparison ast, "c#{sign}le#{t.base_type}"
Greater: (ast)=>
t = get_type(ast.lhs)
sign = (t.base_type == 's' or t.base_type == 'd') and "" or "s"
return @add_comparison ast, "c#{sign}gt#{t.base_type}"
GreaterEq: (ast)=>
t = get_type(ast.lhs)
sign = (t.base_type == 's' or t.base_type == 'd') and "" or "s"
return @add_comparison ast, "c#{sign}ge#{t.base_type}"
Equal: (ast)=>
lhs_type, rhs_type = get_type(ast.lhs), get_type(ast.rhs)
if lhs_type\is_a(rhs_type) or rhs_type\is_a(lhs_type)
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
result = @fresh_local "comparison"
if rhs_type == Types.NilType and (lhs_type.base_type == 's' or lhs_type.base_type == 'd')
@add "#{result} =w cuo#{lhs_type.base_type} #{lhs_reg}, #{lhs_type.base_type}_0.0\n"
elseif lhs_type == Types.NilType and (rhs_type.base_type == 's' or rhs_type.base_type == 'd')
@add "#{result} =w cuo#{rhs_type.base_type} #{rhs_reg}, #{rhs_type.base_type}_0.0\n"
else
@add "#{result} =w ceq#{lhs_type.base_type} #{lhs_reg}, #{rhs_reg}\n"
return result
else
return @add_comparison ast, "ceq#{lhs_type.base_type}"
NotEqual: (ast)=>
lhs_type, rhs_type = get_type(ast.lhs), get_type(ast.rhs)
if lhs_type\is_a(rhs_type) or rhs_type\is_a(lhs_type)
lhs_reg,rhs_reg = @add_values ast.lhs, ast.rhs
result = @fresh_local "comparison"
if rhs_type == Types.NilType and (lhs_type.base_type == 's' or lhs_type.base_type == 'd')
@add "#{result} =w co#{lhs_type.base_type} #{lhs_reg}, #{lhs_type.base_type}_0.0\n"
elseif lhs_type == Types.NilType and (rhs_type.base_type == 's' or rhs_type.base_type == 'd')
@add "#{result} =w co#{rhs_type.base_type} #{rhs_reg}, #{rhs_type.base_type}_0.0\n"
else
@add "#{result} =w cne#{lhs_type.base_type} #{lhs_reg}, #{rhs_reg}\n"
return result
else
return @add_comparison ast, "cnel"
FnCall: (ast, skip_ret=false)=>
if ast.fn.__inline_method
arg_types = {ast.fn.value.__type}
for arg in *ast
if arg.__tag == "KeywordArg"
arg_types[arg.name[0]] = arg.value.__type
else
table.insert arg_types, arg.__type
matches, err = ast.fn.__type\matches(arg_types)
node_assert matches, ast, err
return ast.fn.__inline_method(ast, @, skip_ret)
fn_type = get_type ast.fn, true
fn_reg = @add_values ast.fn
node_assert fn_type\is_a(Types.FnType), ast.fn, "This is not a function, it's a #{fn_type or "???"}"
arg_types,arg_text = {},{}
for arg in *ast
if arg.__tag == "KeywordArg"
arg_types[arg.name[0]] = get_type(arg.value)
table.insert arg_text, "#{arg.name[0]}=#{get_type(arg.value)\verbose_type!}"
else
table.insert arg_types, get_type(arg)
table.insert arg_text, "#{get_type(arg)\verbose_type!}"
if ast.fn.__method
table.insert arg_types, 1, (get_type(ast.fn.value, true))
table.insert arg_text, 1, "#{get_type(ast.fn.value, true)}"
node_assert fn_type\matches(arg_types), ast,
"This function is being called with #{ast.fn[0]}(#{concat arg_text, ", "}) but is defined as #{fn_type}"
kw_args = {}
pos_args = {}
if ast.fn.__method
arg_reg = @add_value ast.fn.value
table.insert pos_args, {reg: arg_reg, type: get_type(ast.fn.value), node: ast.fn.value}
for arg in *ast
if arg.__tag == "KeywordArg"
arg_reg = @add_value arg.value
kw_args[arg.name[0]] = arg_reg
else
arg_reg = @add_value arg
table.insert pos_args, {reg: arg_reg, type: get_type(arg), node:arg}
local arg_list
if fn_type.arg_names
arg_list = {}
assert fn_type.arg_names, "No arg names: #{fn_type}"
for i,name in ipairs fn_type.arg_names
arg_reg = kw_args[name] or (table.remove(pos_args, 1) or {}).reg
if not arg_reg
arg_reg = @fresh_local name
@set_nil fn_type.arg_types[i], arg_reg
table.insert arg_list, "#{fn_type.arg_types[i].base_type} #{arg_reg}"
else
node_assert not next(kw_args), ast, "Keyword arguments supplied to a function that doesn't have names for its arguments"
for _ in *fn_type.arg_types
table.remove(pos_args, 1)
if #pos_args > 0
node_assert fn_type.varargs, pos_args[1].node, "The arguments from here onwards are not defined in the function signature: #{fn_type}"
table.insert(arg_list, #fn_type.arg_types+1, "...")
for arg in *pos_args
table.insert arg_list, "#{arg.type.base_type} #{arg.reg}"
if not arg_list
arg_list = {}
for arg in *ast
node_assert arg.__tag != "KeywordArg", arg, "Keyword arguments are not allowed here"
arg_reg, arg_code = @add_value arg
table.insert arg_list, "#{get_type(arg).base_type} #{arg_reg}"
if skip_ret
@add "call #{fn_reg}(#{concat arg_list, ", "})\n"
return
ret_reg = @fresh_local "return"
ret_type = fn_type and fn_type.return_type or get_type(ast)
@add "#{ret_reg} =#{ret_type.base_type} call #{fn_reg}(#{concat arg_list, ", "}) # Ret type: #{ret_type} base: #{ret_type.base_type}\n"
return ret_reg
Lambda: (ast)=>
node_assert ast.__register, ast, "Unreferenceable lambda"
return ast.__register
Struct: (ast)=>
t = get_type ast, true
ret = @fresh_local "#{t.name\lower!}"
@add "#{ret} =l call $gc_alloc(l #{t.memory_size})\n"
i = 0
unpopulated = {n,memb for n,memb in pairs t.members}
for field in *ast
memb = if field.name
t.members[field.name[0]]
else
i += 1
t.members[i] or t.sorted_members[i]
node_assert memb, field, "Not a valid struct member of #{t\verbose_type!}"
m_t = get_type field.value
node_assert m_t\is_a(memb.type), field, "Expected this value to have type #{memb.type\verbose_type!}, but got #{m_t\verbose_type!}"
loc = @fresh_local "#{t\id_str!\lower!}.#{memb.name}.loc"
@add "#{loc} =l add #{ret}, #{memb.offset}\n"
val_reg = @add_value field.value
if memb.inline
@add "call $memcpy(l #{loc}, l #{val_reg}, l #{memb.type.memory_size})\n"
else
@add "#{m_t.store} #{val_reg}, #{loc}\n"
unpopulated[memb.name] = nil
for name,memb in pairs unpopulated
continue unless memb.type\is_a(Types.OptionalType)
unpopulated[name] = nil
continue if memb.type.nil_value == 0
loc = @fresh_local "#{t\id_str!\lower!}.#{memb.name}.loc"
@add "#{loc} =l add #{ret}, #{memb.offset}\n"
@add "#{memb.type.store} #{memb.type.nil_value}, #{loc}\n"
for name,memb in pairs unpopulated
node_error ast, "#{name} is a required field for #{t.name}, but was not specified"
return ret
Union: (ast)=>
t = get_type ast
ret = @fresh_local "#{t.name\lower!}"
@add "#{ret} =l call $gc_alloc(l #{t.memory_size})\n"
member = node_assert t.members[ast.member[0]], ast, "Not a valid union member name: #{ast.member[0]} in #{t\verbose_type!}"
@add "storel #{member.index}, #{ret}\n"
val_reg = @add_value value
val_loc = @fresh_local "val.loc"
@add "#{val_loc} =l add #{ret}, 8\n"
@add "storel #{val_reg}, #{val_loc}\n"
return ret
If: (ast)=>
t = get_type(ast)
ret = t != Types.Abort and @fresh_local("if.value") or nil
end_label,false_label = @fresh_labels "if.end", "if.else"
for cond in *ast
r = @add_value cond.condition
true_label = @fresh_label "if.true"
@check_truthiness get_type(cond.condition), r, true_label, false_label
@add_label true_label
block_type = get_type(cond.body)
if block_type == Types.Abort
@add_stmt cond.body
elseif block_type == Types.NilType
@add_stmt cond.body
@set_nil t, ret unless @ends_with_jump!
else
block_reg = @add_value cond.body
@add "#{ret} =#{block_type.base_type} copy #{block_reg}\n" unless @ends_with_jump!
@add "jmp #{end_label}\n" unless @ends_with_jump!
@add_label false_label
false_label = @fresh_label "if.else"
if ast.elseBody
else_type = get_type(ast.elseBody)
if else_type == Types.Abort
@add_stmt ast.elseBody
elseif else_type == Types.NilType
@add_stmt ast.elseBody
@set_nil(t, ret) unless t == Types.Abort or @ends_with_jump!
else
block_reg = @add_value ast.elseBody
@add "#{ret} =#{else_type.base_type} copy #{block_reg}\n" unless @ends_with_jump!
@add "jmp #{end_label}\n" unless @ends_with_jump!
else
@set_nil(t, ret) unless t == Types.Abort or @ends_with_jump!
@add_label end_label
return ret
Do: (ast)=>
end_label,next_label = @fresh_labels "do.end", "do.else"
t = get_type(ast)
ret = t != Types.Abort and @fresh_local("do.value") or nil
@add "\n# Do block : #{t}\n"
@set_nil(t, ret) if t != Types.Abort and t\is_a(Types.OptionalType)
for i,block in ipairs ast
@add "# Do block: #{block[0]\gsub("^[ \n]*","")\gsub("\n[ ]*", "; ")}\n"
for jmp in coroutine.wrap(-> each_tag(block, "Stop"))
if not jmp.target or jmp.target[0] == "do"
jmp.jump_label = end_label
for jmp in coroutine.wrap(-> each_tag(block, "Skip"))
if not jmp.target or jmp.target[0] == "do"
jmp.jump_label = next_label
block_type = get_type(block)
if block_type == Types.Abort
@add_stmt block
elseif block_type == Types.NilType
@add_stmt block
@set_nil t, ret
else
block_reg = @add_value block
@add "#{ret} =#{block_type.base_type} copy #{block_reg}\n"
@add "jmp #{end_label}\n" unless @ends_with_jump!
@add "# End do-block\n"
if i < #ast
@add_label next_label
next_label = @fresh_label "do.else"
@add_label next_label
@add "jmp #{end_label}\n"
@add_label end_label
return ret
When: (ast)=>
t = get_type(ast)
what_type = get_type ast.what
ret = t != Types.Abort and @fresh_local("when.value") or nil
what_reg = @add_value ast.what
end_label,next_case,next_body = @fresh_labels "when.end","when.case","when.body"
match_reg = @fresh_local "when.matches"
@add "jmp #{next_case}\n"
for branch in *ast
for case in *branch.cases
node_assert get_type(case)\is_a(what_type), case, "'when' value is not a #{what_type}"
@add "#{next_case}\n"
next_case = @fresh_label "when.case"
case_reg = @add_value case
@add "#{match_reg} =l ceql #{what_reg}, #{case_reg}\n"
@add "jnz #{match_reg}, #{next_body}, #{next_case}\n"
@add_label next_body
next_body = @fresh_label "when.body"
block_type = get_type(branch.body)
if block_type == Types.Abort
@add_stmt branch.body
elseif block_type == Types.NilType
@add_stmt branch.body
@set_nil t, ret
else
block_reg = @add_value branch.body
@add "#{ret} =#{block_type.base_type} copy #{block_reg}\n" unless t == Types.Abort
@add "jmp #{end_label}\n" unless @ends_with_jump!
if ast.elseBody
@add_label next_case
else_type = get_type(ast.elseBody)
if else_type == Types.Abort
@add_stmt ast.elseBody
elseif else_type == Types.NilType
@add_stmt ast.elseBody
@set_nil(t, ret) unless t == Types.Abort
else
block_reg = @add_value ast.elseBody
@add "#{ret} =#{else_type.base_type} copy #{block_reg}\n"
@add "jmp #{end_label}\n" unless @ends_with_jump!
else
@add_label next_case
@set_nil(t, ret)
@add "jmp #{end_label}\n"
@add_label end_label
return ret
Block: (ast)=>
for i=1,#ast-1
@add_stmt(ast[i])
last_reg = @add_value ast[#ast]
return last_reg
Fail: (ast)=>
@add_stmt ast
return 0
Return: (ast)=>
@add_stmt ast
return 0
Skip: (ast)=>
@add_stmt ast
return 0
Stop: (ast)=>
@add_stmt ast
return 0
Use: (ast)=>
name = ast.name[0]
mod = @fresh_local name
@add "#{mod} =l call $bl_use(l #{@get_string_reg @env.filename, "current_file"}, l #{@get_string_reg name, name})\n"
return mod
stmt_compilers =
Block: (ast)=>
for stmt in *ast
@add_stmt stmt
Use: (ast)=>
assert ast.__imports
name = ast.name[0]
mod = @fresh_local name
@add "#{mod} =l call $bl_use(l #{@get_string_reg @env.filename, "current_file"}, l #{@get_string_reg name, name})\n"
success_label,fail_label,done_label = @fresh_labels "use.success","use.fail","use.done"
for i,mem in ipairs ast.__imports
loc = @fresh_local "#{name}.#{mem[0]}.loc"
@add "#{loc} =l add #{mod}, #{8*(i-1)}\n"
tmp = @fresh_local "#{name}.#{mem[0]}"
@add "#{tmp} =#{mem.__type.base_type} #{mem.__type.load} #{loc}\n"
@add "storel #{tmp}, #{mem.__location}\n"
Declaration: (ast)=>
value_type = get_type ast.value, true
val_reg = @add_value ast.value
if ast.var.__register
@add "#{ast.var.__register} =#{value_type.base_type} copy #{val_reg}\n"
elseif ast.var.__location
@add "#{value_type.store} #{val_reg}, #{ast.var.__location}\n"
else
node_error ast.var, "Undefined variable"
Assignment: (ast)=>
node_assert #ast.lhs == #ast.rhs, ast.rhs, "Incorrect number of values on right hand side of assignment. Expected #{#ast.lhs}, but got #{#ast.rhs}"
lhs_stores = {}
for i,lhs in ipairs ast.lhs
rhs = ast.rhs[i]
lhs_type,rhs_type = get_type(lhs), get_type(rhs)
if lhs.__tag == "Var"
node_assert rhs_type\is_a(lhs_type), rhs, "Assignment value is type #{rhs_type}, but it's being assigned to something with type #{lhs_type}"
node_assert lhs.__register or lhs.__location, lhs, "Undefined variable"
if lhs.__register
table.insert lhs_stores, (rhs_reg)->
"#{lhs.__register} =#{lhs_type.base_type} copy #{rhs_reg}\n"
elseif lhs.__location
table.insert lhs_stores, (rhs_reg)->
"#{lhs_type.store} #{rhs_reg}, #{lhs.__location}\n"
continue
node_assert lhs.__tag == "IndexedTerm", lhs, "Expected a Var or an indexed value"
t = get_type(lhs.value)
is_optional = t\is_a(Types.OptionalType)
t = t.nonnil if is_optional
if t\is_a(Types.ListType)
index_type = get_type(lhs.index)
list_reg,index_reg = @add_values lhs.value, lhs.index
if index_type\is_a(Types.Int) or index_type == Types.OptionalType(Types.Int)
table.insert lhs_stores, (rhs_reg)->
code = @new_code!
nonnil_label, end_label = @fresh_labels "if.nonnil", "if.nonnil.done"
code\check_nil get_type(lhs.value), list_reg, nonnil_label, end_label
code\add_label nonnil_label
not_too_low,not_too_high = @fresh_labels "not.too.low", "not.too.high"
len, bounds_check = @fresh_locals "len", "bounds.check"
unless index_reg\match("^%d+$")
code\add "#{bounds_check} =w csgel #{index_reg}, 1\n"
code\add "jnz #{bounds_check}, #{not_too_low}, #{end_label}\n"
code\add "#{not_too_low}\n"
code\add "#{len} =l loadl #{list_reg}\n"
code\add "#{bounds_check} =w cslel #{index_reg}, #{len}\n"
code\add "jnz #{bounds_check}, #{not_too_high}, #{end_label}\n"
code\add_label not_too_high
p = @fresh_local "p"
code\add "#{p} =l add #{list_reg}, 8\n"
code\add "#{p} =l loadl #{p}\n"
offset = @fresh_local "offset"
code\add "#{offset} =l sub #{index_reg}, 1\n"
code\add "#{offset} =l mul #{offset}, #{t.item_type.bytes}\n"
code\add "#{p} =l add #{p}, #{offset}\n"
if rhs_type.base_type == "d"
rhs_casted = @fresh_local "list.item.float"
code\add "#{rhs_casted} =d cast #{rhs_reg}\n"
rhs_reg = rhs_casted
code\add "#{t.item_type.store} #{rhs_reg}, #{p}\n"
code\add "jmp #{end_label}\n"
code\add "#{end_label}\n"
return code
elseif index_type\is_a(Types.Range)
node_error lhs.index, "Assigning to list slices is not supported."
else
node_error lhs.index, "Index is #{index_type} instead of Int or Range"
elseif t\is_a(Types.TableType)
key_type = get_type(lhs.index)
tab_reg,key_reg = @add_values lhs.value, lhs.index
TableMethods = require 'table_methods'
table.insert lhs_stores, (rhs_reg)->
_,code2 = TableMethods.methods.set {tab_reg, key_reg, rhs_reg}, @, true, t
return code2
elseif t\is_a(Types.StructType)
memb = if lhs.index.__tag == "FieldName"
member_name = lhs.index[0]
node_assert t.members[member_name], lhs.index, "Not a valid struct member of #{t}"
t.members[member_name]
elseif lhs.index.__tag == "Int"
i = tonumber(lhs.index[0])
node_assert t.members, lhs.index, "#{t} only has members between 1 and #{#t.members}"
t.members[i]
else
node_error lhs.index, "Structs can only be indexed by a field name or Int literal"
struct_reg = @add_value lhs.value
table.insert lhs_stores, (rhs_reg)->
nonnil_label, end_label = @fresh_labels "if.nonnil", "if.nonnil.done"
code = @new_code!
code\check_nil get_type(lhs.value), struct_reg, nonnil_label, end_label
code\add_label nonnil_label
loc = @fresh_local "member.loc"
code\add "#{loc} =l add #{struct_reg}, #{memb.offset}\n"
if memb.inline
code\add "call $memcpy(l #{loc}, l #{rhs_reg}, l #{assert memb.type.memory_size})\n"
else
code\add "#{rhs_type.store} #{rhs_reg}, #{loc}\n"
code\add "jmp #{end_label}\n"
code\add_label end_label
return code
else
node_error lhs.value, "Only Lists and Structs are mutable, not #{t}"
assignments = @new_code!
for i=1,#ast.rhs
rhs_reg = @add_value ast.rhs[i]
if #ast.rhs > 1
rhs_copy = @fresh_local "rhs.#{i}.copy"
@add "#{rhs_copy} =#{get_type(ast.rhs[i]).base_type} copy #{rhs_reg}\n"
assignments\add lhs_stores[i](rhs_copy)
else
assignments\add lhs_stores[i](rhs_reg)
@add assignments
AddUpdate: (ast)=> @add_value ast
SubUpdate: (ast)=> @add_value ast
MulUpdate: (ast)=> @add_value ast
DivUpdate: (ast)=> @add_value ast
OrUpdate: (ast)=> @add_value ast
AndUpdate: (ast)=> @add_value ast
ButWithUpdate: (ast)=> @add_value ast
MethodCallUpdate: (ast)=> @add_value ast
FnDecl: (ast)=>
ConvertDecl: (ast)=>
Extern: (ast)=>
Macro: (ast)=>
Pass: (ast)=>
Fail: (ast)=>
if ast.message
t = get_type(ast.message)
node_assert t\is_a(Types.OptionalType(Types.String)), ast.message,
"Failure messages must be a String or nil, not #{get_type ast.message}"
msg = @add_value ast.message
fail_label,empty_label = @fresh_labels "failure", "empty.message"
@add "jnz #{msg}, #{fail_label}, #{empty_label}\n"
@add "#{empty_label}\n"
@add "#{msg} =l copy #{@get_string_reg("Unexpected failure!", "default.failure")}\n"
@add "jmp #{fail_label}\n#{fail_label}\n"
@add "call $dprintf(l 2, l #{@get_string_reg(context_err(ast, "%s", 2).."\n", "failure.message")}, l #{msg})\n"
@add "call $_exit(l 1)\n"
else
@add "call $dprintf(l 2, l #{@get_string_reg(context_err(ast, "A failure occurred!", 2).."\n", "failure.message")})\n"
@add "call $_exit(l 1)\n"
TypeDeclaration: (ast)=> ""
StructDeclaration: (ast)=>
for staticvar in *ast
if staticvar.__tag == "Declaration"
@add_stmt staticvar
UnionDeclaration: (ast)=>
for staticvar in *ast
if staticvar.__tag == "Declaration"
@add_stmt staticvar
EnumDeclaration: (ast)=> ""
UnitDeclaration: (ast)=> ""
Export: (ast)=> ""
FnCall: (ast)=>
ret_type = get_type(ast)
if ret_type
node_assert ret_type == Types.Abort or ret_type == Types.NilType, ast, "Return value (#{ret_type}) is not being used"
code = @new_code!
code\add_value ast
@add (tostring(code)\gsub("[^\n]- (call [^\n]*\n)$", "%1"))
Return: (ast)=>
if ast.value
reg = @add_value ast.value
if get_type(ast.value)\is_a(Types.Abort)
@add "ret\n"
else
@add "ret #{reg}\n"
else
@add "ret\n"
@add_label @fresh_label("unreachable")
Stop: (ast)=>
node_assert ast.jump_label, ast, "'stop' statement should only be used inside a loop"
@add "jmp #{ast.jump_label}\n"
@add_label @fresh_label("unreachable")
Skip: (ast)=>
node_assert ast.jump_label, ast, "'skip' statement should only be used inside a loop"
@add "jmp #{ast.jump_label}\n"
@add_label @fresh_label("unreachable")
Do: (ast)=> @add_value ast
If: (ast)=> @add_value ast
When: (ast)=> @add_value ast
Repeat: (ast)=> @add_repeat_loop ast
While: (ast)=> @add_while_loop ast
For: (ast)=>
iter_reg = @add_value ast.iterable
key_reg, value_reg = if ast.index and ast.val
ast.index.__register, ast.val.__register
elseif ast.val and ast.iterable.__type\works_like_a(Types.TableType)
ast.val.__register, nil
elseif ast.val
nil, ast.val.__register
else
nil, nil
@add_for_loop {
type: ast.iterable.__type, :iter_reg, :key_reg, :value_reg, scope:{ast.body, ast.between},
make_body: ->
if ast.body then
code = @new_code!
code\add_stmt ast.body
code
else
""
make_between: ->
if ast.between then
code = @new_code!
code\add_stmt ast.between
code
else
""
}
return code
compile_prog = (ast, filename)->
env = Environment(filename)
return tostring(env\compile_program(ast, filename))
return {:compile_prog}
|