From d0259bd6a2e1213316e8ee79122765dcdb1ac918 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 16 Oct 2020 09:14:51 +0100 Subject: [PATCH] Parse error table --- comerr/comerr.py | 283 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100755 comerr/comerr.py diff --git a/comerr/comerr.py b/comerr/comerr.py new file mode 100755 index 0000000..cd7c7f9 --- /dev/null +++ b/comerr/comerr.py @@ -0,0 +1,283 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# Parse a comerr table into a series of throwable objects and a function that +# throws the appropriate one for an abort code. +# + +__copyright__ = """ +Copyright (C) 2020 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import sys +import keyword +import time +import os + +# +# Convert the error table name to the base value. The name is a base-64 +# encoding, but without a 0. +# +def calc_base_from_name(name): + charmap = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" + mod_base = 1000000 + table_number = 0 + + for i in range(0, len(name)): + c = name[i] + v = charmap.find(c) + if v == -1: + print("Can't map name char ", c) + exit(1) + table_number *= 64 + table_number += v + return table_number * 256 + + +class error: + def __init__(self, name, string): + self.source = None # Current source file + self.lineno = None # Current line number + self.symbol = name # Symbolic name + self.text = string # Display string + self.value = None # Abort code + +class error_table: + def __init__(self, name, errors): + self.name = name # Name of error table + self.base = calc_base_from_name(name) # Base error number + self.errors = errors # List of errors + val = self.base + for ec in errors: + ec.value = val + val += 1 + +########################################################################## +# # +# Lexical analysis # +# # +########################################################################## +import ply.lex as lex + +keywords = ( + "error_table", + "ec", + "end", +) + +# Required by lex. Each token also allows a function t_. +tokens = tuple([t.upper() for t in keywords]) + ( + "ID", "STRING", + # , + "COMMA", "NEWFILE" +) + +# t_ functions are used by lex. They are called with t.value==, and t.type==. They expect a return value +# with attribute type= + +# Tell lexer to ignore Whitespace. +t_ignore = " \t" + +def t_NEWFILE(t): + r'__NEWFILE__ [^\n]*' + t.lexer.source = t.value[12:] + t.lexer.lineno = 0 + return t + +def t_ID(t): + r'[A-Za-z][A-Za-z0-9_]*' + if t.value in keywords: + t.type = t.value.upper() + return t + +def t_STRING(t): + r'\"(\\.|[^"])*\"' + return t + +# Tokens +t_COMMA = r',' + +def t_newline(t): + r'\n+' + t.lexer.lineno += t.value.count("\n") + +def t_error(t): + print("Illegal character {:s} at {:d} type {:s}".format(repr(t.value[0]), + t.lineno, t.type)) + t.lexer.skip(1) + +# Build the lexer +lex.lex(debug=0) + +########################################################################## +# # +# Yacc Parsing Info # +# # +########################################################################## + +def p_specification(t): + '''specification : NEWFILE definition''' + t[0] = t[2] + +def p_definition(t): + '''definition : ERROR_TABLE ID error_list END''' + t[0] = error_table(t[2], t[3]) + +def p_error_list_1(t): + '''error_list : ec''' + t[0] = [ t[1] ] + +def p_error_list_2(t): + '''error_list : error_list ec''' + t[1].append(t[2]) + t[0] = t[1] + +def p_ec(t): + '''ec : EC ID COMMA STRING''' + t[0] = error(t[2], t[4]) + +def p_error(t): + global error_occurred + error_occurred = True + if t: + print(t) + print("Syntax error at '{:s}' (lineno {:d})".format(t.value, t.lineno)) + else: + print("Syntax error: unexpectedly hit EOF") + +# Parse the input data with yacc +import ply.yacc as yacc +yacc.yacc(debug=0) + +########################################################################## +# # +# Global Variables # +# # +########################################################################## + +error_occurred = False # Parsing of infile status + +########################################################################## +# # +# Preprocessor # +# # +########################################################################## +def preprocess(data, filename): + lines = data.splitlines() + + # Remove comments + for i in range(0, len(lines)): + l = lines[i] + p = l.find("#") + if p != -1: + lines[i] = l[:p] + + lines.insert(0, "__NEWFILE__ {:s}".format(filename)) + return "\n".join(lines) + +########################################################################## +# # +# Main Loop # +# # +########################################################################## +def parse(infile, debug=False): + f = open(infile, encoding="utf-8") + data = f.read() + f.close() + + data = preprocess(data, infile) + + print("Parsing", infile); + global yacc + p = yacc.parse(data, debug=debug) + + if error_occurred: + print + print("Error occurred, did not write output files") + return False + return p + +# +# Render the result +# +def rxhdr(*va): + for i in va: + sys.stdout.write(str(i)) + +def rxsrc(*va): + for i in va: + sys.stdout.write(str(i)) + +def rxsrcf(*va): + sys.stdout.write(fmt.format(*va)) + +def render(tables): + rxhdr("namespace comerr {\n") + for et in tables: + rxhdr("\n") + rxhdr("namespace comerr::", et.name, " {\n") + for ec in et.errors: + ecclass = ec.symbol + rxhdr("class ", ecclass, " : public rxrpc::rx_abort {\n"); + rxhdr("\tpublic: ", ecclass, "(int ac, const char *op)\n") + rxhdr("\t: rx_abort(", ec.value, ", \"", ec.symbol, "\", ", ec.text, ", op) {}\n") + rxhdr("};\n") + rxhdr("} /* namespace comerr::", et.name, " */\n") + rxhdr("extern void comerr_throw_abort(int ac, const char *op=NULL);\n"); + rxhdr("} /* namespace comerr */\n") + + # Emit an abort throwing function + rxsrc("\n") + rxsrc("namespace comerr {\n") + rxsrc("\n") + rxsrc("void comerr_throw_abort(int ac, const char *op)\n"); + rxsrc("{\n") + rxsrc("\tswitch (ac) {\n") + for et in tables: + for ec in et.errors: + ecclass = ec.symbol + rxsrc("\tcase ", ec.symbol, ":\tthrow ", et.name, "::", ecclass, "(op);\n") + + rxsrc("\tdefault: rxrpc::throw_abort(ac, op);\n") + rxsrc("\t}\n") + rxsrc("}\n") + rxsrc("\n") + rxsrc("} /* end namespace comerr */\n") + +# +# Section: main +# +def run(files, out_file_prefix): + tables = [] + + for f in files: + et = parse(f) + if not et: + break + tables.append(et) + render(tables) + +# +# Drive the program if executed as a standalone process +# +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: {:s} *".format(sys.argv[0])) + sys.exit(1) + + run(sys.argv[1:], "") -- 2.49.0