| # Copyright (c) 2003-2005 The Regents of The University of Michigan |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer; |
| # redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution; |
| # neither the name of the copyright holders nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import os |
| import sys |
| import re |
| import traceback |
| |
| # get type names |
| from types import * |
| |
| from ply import lex |
| from ply import yacc |
| |
| ########################################################################## |
| # |
| # Base classes for use outside of the assembler |
| # |
| ########################################################################## |
| |
| |
| class MicroContainer: |
| def __init__(self, name): |
| self.microops = [] |
| self.name = name |
| self.directives = {} |
| self.micro_classes = {} |
| self.labels = {} |
| |
| def add_microop(self, mnemonic, microop): |
| microop.mnemonic = mnemonic |
| microop.micropc = len(self.microops) |
| self.microops.append(microop) |
| |
| def __str__(self): |
| string = "%s:\n" % self.name |
| for microop in self.microops: |
| string += " %s\n" % microop |
| return string |
| |
| |
| class CombinationalMacroop(MicroContainer): |
| pass |
| |
| |
| class RomMacroop: |
| def __init__(self, name, target): |
| self.name = name |
| self.target = target |
| |
| def __str__(self): |
| return "%s: %s\n" % (self.name, self.target) |
| |
| |
| class Rom(MicroContainer): |
| def __init__(self, name): |
| super().__init__(name) |
| self.externs = {} |
| |
| |
| ########################################################################## |
| # |
| # Support classes |
| # |
| ########################################################################## |
| |
| |
| class Label(object): |
| def __init__(self): |
| self.extern = False |
| self.name = "" |
| |
| |
| class Block(object): |
| def __init__(self): |
| self.statements = [] |
| |
| |
| class Statement(object): |
| def __init__(self): |
| self.is_microop = False |
| self.is_directive = False |
| self.params = "" |
| |
| |
| class Microop(Statement): |
| def __init__(self): |
| super().__init__() |
| self.mnemonic = "" |
| self.labels = [] |
| self.is_microop = True |
| |
| |
| class Directive(Statement): |
| def __init__(self): |
| super().__init__() |
| self.name = "" |
| self.is_directive = True |
| |
| |
| ########################################################################## |
| # |
| # Functions that handle common tasks |
| # |
| ########################################################################## |
| |
| |
| def print_error(message): |
| print() |
| print("*** %s" % message) |
| print() |
| |
| |
| def handle_statement(parser, container, statement): |
| if statement.is_microop: |
| if statement.mnemonic not in parser.microops.keys(): |
| raise Exception( |
| "Unrecognized mnemonic: {}".format(statement.mnemonic) |
| ) |
| parser.symbols[ |
| "__microopClassFromInsideTheAssembler" |
| ] = parser.microops[statement.mnemonic] |
| try: |
| microop = eval( |
| "__microopClassFromInsideTheAssembler(%s)" % statement.params, |
| {}, |
| parser.symbols, |
| ) |
| except: |
| print_error( |
| "Error creating microop object with mnemonic %s." |
| % statement.mnemonic |
| ) |
| raise |
| try: |
| for label in statement.labels: |
| container.labels[label.text] = microop |
| if label.is_extern: |
| container.externs[label.text] = microop |
| container.add_microop(statement.mnemonic, microop) |
| except: |
| print_error("Error adding microop.") |
| raise |
| elif statement.is_directive: |
| if statement.name not in container.directives.keys(): |
| raise Exception( |
| "Unrecognized directive: {}".format(statement.name) |
| ) |
| parser.symbols[ |
| "__directiveFunctionFromInsideTheAssembler" |
| ] = container.directives[statement.name] |
| try: |
| eval( |
| "__directiveFunctionFromInsideTheAssembler(%s)" |
| % statement.params, |
| {}, |
| parser.symbols, |
| ) |
| except: |
| print_error("Error executing directive.") |
| print(container.directives) |
| raise |
| else: |
| raise Exception( |
| "Didn't recognize the type of statement {}".format(statement) |
| ) |
| |
| |
| ########################################################################## |
| # |
| # Lexer specification |
| # |
| ########################################################################## |
| |
| # Error handler. Just call exit. Output formatted to work under |
| # Emacs compile-mode. Optional 'print_traceback' arg, if set to True, |
| # prints a Python stack backtrace too (can be handy when trying to |
| # debug the parser itself). |
| def error(lineno, string, print_traceback=False): |
| # Print a Python stack backtrace if requested. |
| if print_traceback: |
| traceback.print_exc() |
| if lineno != 0: |
| line_str = "%d:" % lineno |
| else: |
| line_str = "" |
| sys.exit("%s %s" % (line_str, string)) |
| |
| |
| reserved = ("DEF", "MACROOP", "ROM", "EXTERN") |
| |
| tokens = reserved + ( |
| # identifier |
| "ID", |
| # arguments for microops and directives |
| "PARAMS", |
| "LPAREN", |
| "RPAREN", |
| "LBRACE", |
| "RBRACE", |
| "COLON", |
| "SEMI", |
| "DOT", |
| "NEWLINE", |
| ) |
| |
| # New lines are ignored at the top level, but they end statements in the |
| # assembler |
| states = ( |
| ("asm", "exclusive"), |
| ("params", "exclusive"), |
| ("header", "exclusive"), |
| ) |
| |
| reserved_map = {} |
| for r in reserved: |
| reserved_map[r.lower()] = r |
| |
| # Ignore comments |
| def t_ANY_COMMENT(t): |
| r"\#[^\n]*(?=\n)" |
| |
| |
| def t_ANY_MULTILINECOMMENT(t): |
| r"/\*([^/]|((?<!\*)/))*\*/" |
| |
| |
| # A colon marks the end of a label. It should follow an ID which will |
| # put the lexer in the "params" state. Seeing the colon will put it back |
| # in the "asm" state since it knows it saw a label and not a mnemonic. |
| def t_params_COLON(t): |
| r":" |
| t.lexer.pop_state() |
| return t |
| |
| |
| # Parameters are a string of text which don't contain an unescaped statement |
| # statement terminator, ie a newline or semi colon. |
| def t_params_PARAMS(t): |
| r"([^\n;\\]|(\\[\n;\\]))+" |
| t.lineno += t.value.count("\n") |
| unescapeParamsRE = re.compile(r"(\\[\n;\\])") |
| |
| def unescapeParams(mo): |
| val = mo.group(0) |
| return val[1] |
| |
| t.value = unescapeParamsRE.sub(unescapeParams, t.value) |
| t.lexer.pop_state() |
| return t |
| |
| |
| # An "ID" in the micro assembler is either a label, directive, or mnemonic |
| # If it's either a directive or a mnemonic, it will be optionally followed by |
| # parameters. If it's a label, the following colon will make the lexer stop |
| # looking for parameters. |
| def t_asm_ID(t): |
| r"[A-Za-z_]\w*" |
| t.type = reserved_map.get(t.value, "ID") |
| # If the ID is really "extern", we shouldn't start looking for parameters |
| # yet. The real ID, the label itself, is coming up. |
| if t.type != "EXTERN": |
| t.lexer.push_state("params") |
| return t |
| |
| |
| def t_header_ID(t): |
| r"[A-Za-z_]\w*" |
| return t |
| |
| |
| # If there is a label and you're -not- in the assembler (which would be caught |
| # above), don't start looking for parameters. |
| def t_ANY_ID(t): |
| r"[A-Za-z_]\w*" |
| t.type = reserved_map.get(t.value, "ID") |
| if t.type == "MACROOP": |
| t.lexer.push_state("asm") |
| t.lexer.push_state("header") |
| elif t.type == "ROM": |
| t.lexer.push_state("asm") |
| t.lexer.push_state("header") |
| return t |
| |
| |
| # Braces enter and exit micro assembly |
| def t_header_LBRACE(t): |
| r"\{" |
| t.lexer.pop_state() |
| return t |
| |
| |
| def t_asm_RBRACE(t): |
| r"\}" |
| t.lexer.pop_state() |
| return t |
| |
| |
| # In the micro assembler, do line counting but also return a token. The |
| # token is needed by the parser to detect the end of a statement. |
| def t_asm_NEWLINE(t): |
| r"\n+" |
| t.lineno += t.value.count("\n") |
| return t |
| |
| |
| # A newline or semi colon when looking for params signals that the statement |
| # is over and the lexer should go back to looking for regular assembly. |
| def t_params_NEWLINE(t): |
| r"\n+" |
| t.lineno += t.value.count("\n") |
| t.lexer.pop_state() |
| return t |
| |
| |
| def t_params_SEMI(t): |
| r";" |
| t.lexer.pop_state() |
| return t |
| |
| |
| # Unless handled specially above, track newlines only for line counting. |
| def t_ANY_NEWLINE(t): |
| r"\n+" |
| t.lineno += t.value.count("\n") |
| |
| |
| # Basic regular expressions to pick out simple tokens |
| t_ANY_LPAREN = r"\(" |
| t_ANY_RPAREN = r"\)" |
| t_ANY_SEMI = r";" |
| t_ANY_DOT = r"\." |
| |
| t_ANY_ignore = " \t\x0c" |
| |
| |
| def t_ANY_error(t): |
| error(t.lineno, "illegal character '%s'" % t.value[0]) |
| t.skip(1) |
| |
| |
| ########################################################################## |
| # |
| # Parser specification |
| # |
| ########################################################################## |
| |
| # Start symbol for a file which may have more than one macroop or rom |
| # specification. |
| def p_file(t): |
| "file : opt_rom_or_macros" |
| |
| |
| def p_opt_rom_or_macros_0(t): |
| "opt_rom_or_macros :" |
| |
| |
| def p_opt_rom_or_macros_1(t): |
| "opt_rom_or_macros : rom_or_macros" |
| |
| |
| def p_rom_or_macros_0(t): |
| "rom_or_macros : rom_or_macro" |
| |
| |
| def p_rom_or_macros_1(t): |
| "rom_or_macros : rom_or_macros rom_or_macro" |
| |
| |
| def p_rom_or_macro_0(t): |
| """rom_or_macro : rom_block |
| | macroop_def""" |
| |
| |
| # Defines a section of microcode that should go in the current ROM |
| def p_rom_block(t): |
| "rom_block : DEF ROM block SEMI" |
| if not t.parser.rom: |
| print_error("Rom block found, but no Rom object specified.") |
| raise TypeError("Rom block found, but no Rom object was specified.") |
| for statement in t[3].statements: |
| handle_statement(t.parser, t.parser.rom, statement) |
| t[0] = t.parser.rom |
| |
| |
| # Defines a macroop that jumps to an external label in the ROM |
| def p_macroop_def_0(t): |
| "macroop_def : DEF MACROOP ID LPAREN ID RPAREN SEMI" |
| if not t.parser.rom_macroop_type: |
| print_error( |
| "ROM based macroop found, but no ROM macroop " |
| + "class was specified." |
| ) |
| raise TypeError( |
| "ROM based macroop found, but no ROM macroop " |
| + "class was specified." |
| ) |
| macroop = t.parser.rom_macroop_type(t[3], t[5]) |
| t.parser.macroops[t[3]] = macroop |
| |
| |
| # Defines a macroop that is combinationally generated |
| def p_macroop_def_1(t): |
| "macroop_def : DEF MACROOP ID block SEMI" |
| try: |
| curop = t.parser.macro_type(t[3]) |
| except TypeError: |
| print_error("Error creating macroop object.") |
| raise |
| for statement in t[4].statements: |
| handle_statement(t.parser, curop, statement) |
| t.parser.macroops[t[3]] = curop |
| |
| |
| # A block of statements |
| def p_block(t): |
| "block : LBRACE statements RBRACE" |
| block = Block() |
| block.statements = t[2] |
| t[0] = block |
| |
| |
| def p_statements_0(t): |
| "statements : statement" |
| if t[1]: |
| t[0] = [t[1]] |
| else: |
| t[0] = [] |
| |
| |
| def p_statements_1(t): |
| "statements : statements statement" |
| if t[2]: |
| t[1].append(t[2]) |
| t[0] = t[1] |
| |
| |
| def p_statement(t): |
| "statement : content_of_statement end_of_statement" |
| t[0] = t[1] |
| |
| |
| # A statement can be a microop or an assembler directive |
| def p_content_of_statement_0(t): |
| """content_of_statement : microop |
| | directive""" |
| t[0] = t[1] |
| |
| |
| # Ignore empty statements |
| def p_content_of_statement_1(t): |
| "content_of_statement :" |
| pass |
| |
| |
| # Statements are ended by newlines or a semi colon |
| def p_end_of_statement(t): |
| """end_of_statement : NEWLINE |
| | SEMI""" |
| pass |
| |
| |
| # Different flavors of microop to avoid shift/reduce errors |
| def p_microop_0(t): |
| "microop : labels ID" |
| microop = Microop() |
| microop.labels = t[1] |
| microop.mnemonic = t[2] |
| t[0] = microop |
| |
| |
| def p_microop_1(t): |
| "microop : ID" |
| microop = Microop() |
| microop.mnemonic = t[1] |
| t[0] = microop |
| |
| |
| def p_microop_2(t): |
| "microop : labels ID PARAMS" |
| microop = Microop() |
| microop.labels = t[1] |
| microop.mnemonic = t[2] |
| microop.params = t[3] |
| t[0] = microop |
| |
| |
| def p_microop_3(t): |
| "microop : ID PARAMS" |
| microop = Microop() |
| microop.mnemonic = t[1] |
| microop.params = t[2] |
| t[0] = microop |
| |
| |
| # Labels in the microcode |
| def p_labels_0(t): |
| "labels : label" |
| t[0] = [t[1]] |
| |
| |
| def p_labels_1(t): |
| "labels : labels label" |
| t[1].append(t[2]) |
| t[0] = t[1] |
| |
| |
| # labels on lines by themselves are attached to the following instruction. |
| def p_labels_2(t): |
| "labels : labels NEWLINE" |
| t[0] = t[1] |
| |
| |
| def p_label_0(t): |
| "label : ID COLON" |
| label = Label() |
| label.is_extern = False |
| label.text = t[1] |
| t[0] = label |
| |
| |
| def p_label_1(t): |
| "label : EXTERN ID COLON" |
| label = Label() |
| label.is_extern = True |
| label.text = t[2] |
| t[0] = label |
| |
| |
| # Directives for the macroop |
| def p_directive_0(t): |
| "directive : DOT ID" |
| directive = Directive() |
| directive.name = t[2] |
| t[0] = directive |
| |
| |
| def p_directive_1(t): |
| "directive : DOT ID PARAMS" |
| directive = Directive() |
| directive.name = t[2] |
| directive.params = t[3] |
| t[0] = directive |
| |
| |
| # Parse error handler. Note that the argument here is the offending |
| # *token*, not a grammar symbol (hence the need to use t.value) |
| def p_error(t): |
| if t: |
| error(t.lineno, "syntax error at '%s'" % t.value) |
| else: |
| error(0, "unknown syntax error", True) |
| |
| |
| class MicroAssembler(object): |
| def __init__(self, macro_type, microops, rom=None, rom_macroop_type=None): |
| self.lexer = lex.lex() |
| self.parser = yacc.yacc(write_tables=False) |
| self.parser.macro_type = macro_type |
| self.parser.macroops = {} |
| self.parser.microops = microops |
| self.parser.rom = rom |
| self.parser.rom_macroop_type = rom_macroop_type |
| self.parser.symbols = {} |
| self.symbols = self.parser.symbols |
| |
| def assemble(self, asm): |
| self.parser.parse(asm, lexer=self.lexer) |
| macroops = self.parser.macroops |
| self.parser.macroops = {} |
| return macroops |