local build_dag build_dag = function(t, dag, i, j, level) if dag == nil then dag = { } end if i == nil then i = 1 end if j == nil then j = #t end if level == nil then level = 0 end if i > j then return end local mid = math.floor((i + j) / 2) dag[t[mid]] = { (build_dag(t, dag, i, mid - 1, level + 1)), (build_dag(t, dag, mid + 1, j, level + 1)) } return t[mid], dag end local append_dag append_dag = function(node, dag, k) local i = k > node and 2 or 1 local next_node = dag[node][i] if next_node then return append_dag(next_node, dag, k) end dag[node][i] = k dag[k] = { } end local build_dag_from_chars build_dag_from_chars = function(s, ...) local t do local _accum_0 = { } local _len_0 = 1 for i = 1, #s do _accum_0[_len_0] = s:sub(i, i) _len_0 = _len_0 + 1 end t = _accum_0 end table.sort(t) local root, dag = build_dag(t) for i = 1, select('#', ...) do append_dag(root, dag, (select(i, ...))) end return root, dag end local check_terminals check_terminals = function(dag, s) for i = 1, #s do local k = s:sub(i, i) assert(not dag[k][1], ('%s has left child node'):format(k)) assert(not dag[k][2], ('%s has right child node'):format(k)) end end local dump dump = function(f, root, dag, level) if level == nil then level = 0 end if dag[root][1] then dump(f, dag[root][1], dag, level + 1) end f:write((' '):rep(level)) f:write(root) f:write('\n') if dag[root][2] then return dump(f, dag[root][2], dag, level + 1) end end local write_opcode_tree do local byte_to_opcode = { } local byte = false for l in assert(io.lines('src/assembler.c')) do if l:match('^%s*char%s+ops%[%]%[4%]') then byte = 0 elseif l:match('%}') then byte = false elseif byte then for opcode in l:gmatch('"([A-Z-][A-Z-][A-Z-])"') do byte_to_opcode[byte] = opcode byte = byte + 1 end end end local order_to_opcode do local _accum_0 = { } local _len_0 = 1 for i = 0, #byte_to_opcode do if byte_to_opcode[i] ~= '---' then _accum_0[_len_0] = byte_to_opcode[i] _len_0 = _len_0 + 1 end end order_to_opcode = _accum_0 end table.sort(order_to_opcode) local root, opcode_to_links = build_dag(order_to_opcode) write_opcode_tree = function(f) for i = 0, #byte_to_opcode do local opcode = byte_to_opcode[i] f:write('\t') if opcode == root then f:write('$root ') elseif opcode ~= '---' then f:write(('$op-%s '):format(opcode:lower())) else f:write(' ') end for j = 1, 2 do if opcode ~= '---' and opcode_to_links[opcode][j] then f:write(('.$op-%s '):format(opcode_to_links[opcode][j]:lower())) else f:write('[ 0000 ] ') end end if i == 0 then f:write('$disasm ') else f:write(' ') end if opcode ~= '---' then f:write(('[ %s ]'):format(opcode)) else f:write('[ ??? ]') end if i == 0 then f:write(' $asm') end f:write('\n') end end end local type_byte type_byte = function(size, has_subtree) local n1 = has_subtree and '8' or '0' local n2 local _exp_0 = size if '1' == _exp_0 then n2 = '1' elseif '2' == _exp_0 then n2 = '3' else n2 = '0' end return n1 .. n2 end local globals = { } local add_globals add_globals = function(root, dag, key_to_label, key_to_contents, pad_before, pad_after) if pad_before == nil then pad_before = '' end if pad_after == nil then pad_after = '' end for k in pairs(dag) do local l = '' if k == root then l = l .. ('@%s\n'):format(key_to_label('root'):gsub('%s', '')) end l = l .. ('@%s '):format(key_to_label(k)) for j = 1, 2 do if dag[k][j] then l = l .. ('.%s '):format(key_to_label(dag[k][j])) else l = l .. ('%s[ 0000 ]%s '):format(pad_before, pad_after) end end l = l .. key_to_contents(k) l = l .. '\n' globals[key_to_label(k):gsub('%s', '')] = l end globals[key_to_label('root'):gsub('%s', '')] = '' end do local root, dag = build_dag_from_chars('{}[]%@$;|=~,.^#"\0', '(', ')') check_terminals(dag, ')') local convert = { ['.'] = 'dot', ['\0'] = 'nul' } local label_name label_name = function(s) return ('first-char-%-3s'):format(convert[s] or s) end local label_value label_value = function(k) return ('[ %02x ]'):format(k:byte()) end add_globals(root, dag, label_name, label_value, ' ', ' ') end local devices = { } local add_device add_device = function(name, fields) local field_sizes do local _tbl_0 = { } for k, size in fields:gmatch('(%S+) (%d+)') do _tbl_0[k] = size end field_sizes = _tbl_0 end field_sizes.pad = nil local field_names do local _accum_0 = { } local _len_0 = 1 for k in pairs(field_sizes) do _accum_0[_len_0] = k _len_0 = _len_0 + 1 end field_names = _accum_0 end table.sort(field_names) local root, dag = build_dag(field_names) local label_name label_name = function(k) return ('l-%-14s'):format(name .. '-' .. k) end local label_value label_value = function(k) return ('%-17s [ %s ] .%s.%s'):format(('[ %s 00 ]'):format(k), type_byte(field_sizes[k], false), name, k) end add_globals(root, dag, label_name, label_value, ' ', ' ') return table.insert(devices, name) end local add_devices add_devices = function() table.sort(devices) local root, dag = build_dag(devices) local label_name label_name = function(k) return ('l-%-14s'):format(k) end local label_value label_value = function(k) return ('%-17s [ %s ] .%s .l-%s-root'):format(('[ %s 00 ]'):format(k), type_byte(0, true), k, k) end return add_globals(root, dag, label_name, label_value, ' ', ' ') end local filename = 'projects/software/assembler.usm' local f = assert(io.open(('%s.tmp'):format(filename), 'w')) local state = 'normal' local machine = { normal = function(l) if l:match('%$disasm .*%$asm') then write_opcode_tree(f) state = 'opcode' elseif l:match('^%@') then if l == '@RESET' then add_devices() end for k in l:gmatch('%@(%S+)') do if globals[k] then f:write(globals[k]) globals[k] = nil return end end f:write(l) return f:write('\n') else if l:match('^%|%x%x%x%x %;') then add_device(l:match('%;(%S+) %{ (.*) %}')) end f:write(l) return f:write('\n') end end, opcode = function(l) if not l:match('%[') then f:write(l) f:write('\n') state = 'normal' end end } for l in assert(io.lines(filename)) do machine[state](l) end for _, l in pairs(globals) do f:write(l) end f:close() assert(0 == os.execute(('mv %s %s.bak'):format(filename, filename))) return assert(0 == os.execute(('mv %s.tmp %s'):format(filename, filename)))