diff options
Diffstat (limited to 'assembler/hasm.py')
-rwxr-xr-x | assembler/hasm.py | 265 |
1 files changed, 254 insertions, 11 deletions
diff --git a/assembler/hasm.py b/assembler/hasm.py index d9f33b2..6bbcf53 100755 --- a/assembler/hasm.py +++ b/assembler/hasm.py @@ -15,17 +15,29 @@ paths = glob.glob(datadir+"/instructions/*/*") paths.sort() +def my_int(size): + base = 10 + if size[:2] == "0x": + base = 16 # int() will remove the 0x + elif size[:-1] == "h": + if size[:2].lower() == "0x": # We don't want int() to silently remove this + raise Exception("Error at line "+str(linenum)+": Parameter size is not a valid number: "+input.split("\n")[instruction["linenum"]]) + base = 16 + elif size[:2] == "0b": + base = 2 # As with 0x, int() will remove this + + return int(size, base) + instructions = {} num_instructions = 0 for file in paths: if os.path.isfile(file) and not os.path.basename(file).endswith("notes.txt"): name = os.path.splitext(os.path.basename(file))[0] - try: - _ = instructions[name] + + if name in instructions: raise Exception("Instruction name conflict") - except KeyError: - pass + instructions[name] = {"path": file, "id": num_instructions} num_instructions += 1 @@ -54,6 +66,16 @@ f = open(thisdir+"/arbitrary_constants.py"); exec(f.read()) f.close() +registers = ["MR", "PR", "FR", "OFR", "RS", "RW", "OIPH", "OIPE", "OIPDE", "OPRH", "OPRE", "OPRDE", "IP", "SP", "BP"] +i=0 +while i < num_general_registers: + registers.append("R"+str(i)) + i += 1 +i=0 +while i < num_segment_registers: + registers.append("SS"+str(i)) + i += 1 + argc = len(sys.argv) print("Number of args given:", argc) @@ -78,12 +100,15 @@ inputf.close() print(input) -# first pass: evaluate instructions, calculate sizes, validate syntax, evaluate label addresses -linenum = 0 +labels = {} + +# first pass: validate instructions, validate syntax (mostly), evaluate what labels exist and their default sizes, split up input to the relevant data +lines = [] # For the next pass + in_queue = False -last_queue_opcode = None -for line in input.split('\n'): - linenum+=1 +linenum = 0 +for line in input.split("\n"): + linenum += 1 line = line.strip() if line == "" or line[0] == ";": @@ -97,6 +122,224 @@ for line in input.split('\n'): raise Exception("Invalid instruction at line "+str(linenum)+": "+line) i+=1 - print(line[0:i]) + command = line[:i] + + i += 1 + + param_start = i + + params = [] + + num_parameters = 0 + was_space = True + escaped = False + in_singlequote = False + in_doublequote = False + in_backtick = False + last_space = i-1 + while i < len(line): + if line[i] == "'": + raise Exception("Error at line "+str(linenum)+": ' has no defined meaning in this syntax yet!") + elif line[i] == '"': + if in_doublequote and not escaped: + in_doublequote = False + elif not in_doublequote: + in_doublequote = True + elif line[i] == "`": + if in_backtick and not escaped: + in_backtick = False + elif not in_backtick: + in_backtick = True + elif line[i] == ";" and was_space: + break # rest is a comment + + if line[i] == " " or line[i] == " ": + old_was_space = was_space + was_space = (not in_singlequote and not in_doublequote and not in_backtick) + if was_space and not old_was_space: + num_parameters = num_parameters + 1 + if line[i-1] == ",": + params.append(line[last_space+1:i-1]) + else: + params.append(line[last_space+1:i]) + + last_space = i + else: + was_space = False + + i += 1 + + + if i == len(line): + num_parameters += 1 + params.append(line[last_space+1:i]) + + if command in instructions: + if num_parameters != instructions[command]['params']: + raise Exception("Error at line "+str(linenum)+": Wrong number of parameters given for this instruction: "+line) + # Not actually doing anything with this on this pass + elif command == "declare": + if in_queue: + raise Exception("Error at line "+str(linenum)+": Cannot declare data inside an instruction queue: "+line) + + pass # Again nothing to do on this pass, but important to have these here so they won't be seen as unknown instructions + elif command == "{": + if num_parameters != 0: + raise Exception("Error at line "+str(linenum)+": { does not take any parameters!") + + if in_queue: + raise Exception("Error at line "+str(linenum)+": Instruction queues cannot be nested!") + in_queue = True + elif command == "}": + if num_parameters != 0: + raise Exception("Error at line "+str(linenum)+": } does not take any parameters!") + + if not in_queue: + raise Exception("Error at line "+str(linenum)+": Found } but no matching { before it!") + in_queue = False + elif command[-1] == ":": + if in_queue: + raise Exception("Error at line "+str(linenum)+": Cannot declare a label inside an instruction queue: "+line) + + if num_parameters != 0: + raise Exception("Error at line "+str(linenum)+": Labels do not take any parameters!") + + label = command[:-1] + + size = current_mode + if len(label) > 0 and label[0] == "{": + i = 1 + size = "" + while i < len(label): + if label[i] == "}": + break + + size += label[i] + + i += 1 + if i == len(label): + raise Exception("Error at line "+str(linenum)+": Label starts with { but doesn't contain a }: "+line) + + label = label[i+1:] + + try: + size = my_int(size) + except ValueError: + raise Exception("Error at line "+str(linenum)+": Label size is not a valid number: "+line) + + if label in labels: + raise Exception("Error at line "+str(linenum)+": Label already declared at line "+str(labels[label]["linenum"])+": "+line) + + labels[label] = {"linenum": linenum, "bits": size} + else: + raise Exception("Unknown instruction at line "+str(linenum)+": "+command) + + lines.append({"command": command, "params": params, "linenum": linenum}) + +if in_queue: + raise Exception("Unterminated instruction queue found at <EOF>!") + +print("First pass results: labels =", labels, ", lines =", lines) + +# second pass: calculate all sizes, calculate label addresses +def get_size(queue): + size = 0 + + size += size_instruction_queue_bits + last_opcode = None + current_opcodes = None + for instruction in queue: + if instruction["command"] != last_opcode: + if max_instructions_per_type != 1: # < 1 makes no sense so I don't care about handling it + last_opcode = instruction["command"] + current_opcodes = 1 + + size += opcode_bit_size + size += num_instructions_bit_size + else: + current_opcodes += 1 + if current_opcodes == max_instructions_per_type: + last_opcode = None + size += bits_per_parameter * instructions[instruction["command"]]["params"] + + size += immediate_bit_size + + for instruction in queue: + for parameter in instruction["params"]: + if parameter[-1] == "]": + tmp = parameter.split("[", maxsplit=1) + if len(tmp) != 2: + raise Exception("Error at line "+str(linenum)+": Parameter ends with ] but no matching [ was found: "+input.split("\n")[instruction["linenum"]]) + parameter = tmp[1][:-1] + if len(parameter) == 0: + raise Exception("Error at line "+str(linenum)+": Invalid parameter: "+input.split("\n")[instruction["linenum"]]) + + this_size = current_mode + if parameter[0] == "{": + i = 1 + this_size = "" + while i < len(parameter): + if parameter[i] == "}": + break + + this_size += parameter[i] + + i += 1 + if i == len(parameter): + raise Exception("Error at line "+str(linenum)+": Parameter starts with { but doesn't contain a }: "+input.split("\n")[instruction["linenum"]]) + + parameter = parameter[i+1:] + + try: + this_size = my_int(this_size) + except ValueError: + raise Exception("Error at line "+str(linenum)+": Parameter size is not a valid number: "+input.split("\n")[instruction["linenum"]]) + elif parameter[-1] == ":": + this_size = labels[parameter[:-1]]["bits"] + + if this_size <= parameter_data_bits: + continue # So you can actually use the 9th or whatever bit even if it doesn't support a full 16 + # For other things, it'll have to be rounded up + + this_size = 2**math.ceil(math.log2(this_size)) + + if parameter[-1] == ":": # Will stuff this into immediate references, unless the size is declared to be <= max allowed directly in param + # Could get away with it if the higher bits would have been 0 anyways, but not going to bother with that yet + size += this_size + elif parameter.upper() in registers: + continue + else: + try: + my_int(parameter) + size += this_size + except ValueError: + raise Exception("Error at line "+str(linenum)+": Unknown parameter: `"+parameter+"': "+input.split("\n")[instruction["linenum"]]) + + size += bits_for_flag_save_definition_size + + return size + +address = 0 + +current_queue = [] +in_queue = False + +for line in lines: + if line["command"] in instructions: + if in_queue: + current_queue.append(line) + else: + address += get_size([line]) + elif line["command"] == "{": + in_queue = True + elif line["command"] == "}": + address += get_size(current_queue) + + current_queue = [] + in_queue = False + elif line["command"] == "declare": + pass # TODO: Declare + elif line["command"][:-1] == ":": + pass # TODO: Labels -# second pass: evaluate parameter values, create binary output +# third pass: calculate parameter values, calculate immediate reference values, create binary output |