From 23f530e458786f4f6a2a0dec0cba74e7dc15da30 Mon Sep 17 00:00:00 2001 From: Dusty DeWeese Date: Mon, 30 Nov 2020 13:30:31 -0800 Subject: [PATCH] Add ANTLR4 grammar and C++ parser Signed-off-by: Dusty DeWeese --- Makefile | 40 +++++ cpp/ParseFasm.cpp | 414 +++++++++++++++++++++++++++++++++++++++++++++ fasm/FasmLexer.g4 | 30 ++++ fasm/FasmParser.g4 | 24 +++ fasm/__init__.py | 303 +++++++++++++++++++++------------ setup.cfg | 5 + setup.py | 18 +- 7 files changed, 727 insertions(+), 107 deletions(-) create mode 100644 cpp/ParseFasm.cpp create mode 100644 fasm/FasmLexer.g4 create mode 100644 fasm/FasmParser.g4 diff --git a/Makefile b/Makefile index 75ffb735..e4c5fac8 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,12 @@ # # SPDX-License-Identifier: ISC +CXXFLAGS += -Wno-attributes -O3 +LDFLAGS += -lantlr4-runtime + +.PHONY: all +all: parse_fasm fasm/parse_fasm.so + PYTHON_FORMAT ?= yapf format: $(IN_ENV) find . -name \*.py $(FORMAT_EXCLUDE) -print0 | xargs -0 -P $$(nproc) yapf -p -i @@ -13,3 +19,37 @@ format: check-license: @./.github/check_license.sh @./.github/check_python_scripts.sh + + +parse_fasm: cpp/ParseFasm.o cpp/FasmParser.o cpp/FasmLexer.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -o $@ + +fasm/parse_fasm.so: cpp/ParseFasm.o cpp/FasmParser.o cpp/FasmLexer.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) $^ -fPIC -shared -o $@ + +cpp/ParseFasm.o: cpp/FasmLexer.h cpp/FasmParser.h + +cpp/FasmParser.cpp \ +cpp/FasmParser.h \ +cpp/FasmParser.interp \ +cpp/FasmParser.tokens \ +cpp/FasmParserBaseListener.cpp \ +cpp/FasmParserBaseListener.h \ +cpp/FasmParserBaseVisitor.cpp \ +cpp/FasmParserBaseVisitor.h \ +cpp/FasmParserListener.cpp \ +cpp/FasmParserListener.h \ +cpp/FasmParserVisitor.cpp \ +cpp/FasmParserVisitor.h : fasm/FasmParser.g4 cpp/FasmLexer.tokens + antlr -Dlanguage=Cpp -visitor -Xexact-output-dir -o cpp $< + +cpp/FasmLexer.cpp \ +cpp/FasmLexer.h \ +cpp/FasmLexer.interp \ +cpp/FasmLexer.tokens : fasm/FasmLexer.g4 + antlr -Dlanguage=Cpp -Xexact-output-dir -o cpp $< + +.PHONY: clean +clean: + rm -f cpp/FasmLexer* cpp/FasmParser* cpp/*.o parse_fasm fasm/parse_fasm.so + diff --git a/cpp/ParseFasm.cpp b/cpp/ParseFasm.cpp new file mode 100644 index 00000000..58496b22 --- /dev/null +++ b/cpp/ParseFasm.cpp @@ -0,0 +1,414 @@ +#include "antlr4-runtime.h" +#include "FasmLexer.h" +#include "FasmParser.h" +#include "FasmParserVisitor.h" + +// External C Interface +// +// These functions serialize the FASM parse tree to an easy to parse +// tag/length/value binary format, where the tag is one byte and +// the length is 4 bytes, in native endianness (typically little.) +extern "C" { + const char *from_string(const char *in, bool hex); + const char *from_file(const char *path, bool hex); +} + +using namespace antlr4; +using namespace antlrcpp; + +// Hex mode is useful for debugging. +// In this mode, binary values are printed as hex values surrounded by < > +bool hex_mode = false; + +// The Num class provides an << operator that either dumps the raw value +// or prints the hex value based on the mode. It also can print a tag. +class Num { +public: + + // Constructors, with optional tag character + Num(char tag, uint32_t num) : num(num), tag(tag) {} + Num(uint32_t num) : num(num), tag(0) {} + + uint32_t num; // The value + char tag; // Tag character + + // The bit width + static constexpr int width = sizeof(num) * 8; + +}; + +// Output stream operator for Num. +// In hex mode, Nums are printed as , otherwise they are written raw. +std::ostream &operator<<(std::ostream &s, const Num &num) { + if (num.tag) s << num.tag; + if (hex_mode) { + s << "<" << std::hex << num.num << ">"; + } else { + s.write(reinterpret_cast(&num.num), sizeof(num.num)); + } + return s; +} + +// The Str class wraps a std::string to provide an << operator +// that includes the tag and length. +struct Str { + + // Takes the tag and string data + Str(char tag, std::string data) : tag(tag), data(data) {} + + char tag; + std::string data; +}; + +// This output stream operator adds the header needed to decode +// a string of unknown length and type. +// Note that some characters are escaped in hex mode to +// avoid confusion. +std::ostream &operator<<(std::ostream &s, const Str &str) { + s << str.tag << Num(str.data.size()); + if (hex_mode) { // escape < \ > + for (char c : str.data) { + if (c == '<' || c == '>' || c == '\\') { + s.put('\\'); + } + s.put(c); + } + } else { + s << str.data; + } + return s; +} + +// Wraps a string in another header, used to aggregate data. +std::string withHeader(char tag, std::string data) { + std::ostringstream header; + header << tag << Num(data.size()); + return header.str() + data; +} + +// Counts characters that don't match the given character. +// Used to count digits skipping '_'. +int count_without(std::string::iterator start, + std::string::iterator end, + char c) { + int count = 0; + auto it = start; + while (it != end) { + if (*it != c) { + count++; + } + it++; + } + return count; +} + +// Calculates the number of leading pad bits are needed +// so that the rightmost bit will be the LSB of a Num. +// e.g. This would be 31 for 33'b0. +int lead_bits(int bits) +{ + return (Num::width - (bits % Num::width)) % Num::width; +} + +// Decode a hex digit. +int from_hex_digit(char c) { + int result = + c >= '0' && c <= '9' ? c - '0' : + c >= 'a' && c <= 'f' ? c - 'a' : + c >= 'A' && c <= 'F' ? c - 'A' : -1; + assert(result >= 0 && result < 16); + return result; +} + + +// FasmParserBaseVisitor is a visitor for the parse tree +// generated by the ANTLR parser. +// It will encode the tree a line at a time and stream out of +// the given std::ostream +class FasmParserBaseVisitor : public FasmParserVisitor { + +public: + + static constexpr size_t kHeaderSize = 5; + + + // The constructor requires a std::ostream to stream encoded lines. + // This is to avoid storing an entire copy of the parse tree in a + // different form. + FasmParserBaseVisitor(std::ostream &out) : out(out) {} + + // Stream out FASM lines. + virtual Any visitFasmFile(FasmParser::FasmFileContext *context) override { + for (auto &line : context->fasmLine()) { + std::string str = visit(line); + if (!str.empty()) { + out << str; + if (hex_mode) out << std::endl; + } + } + return {}; + } + +// Helper macro to convert a rule context into a string +#define GET(x) (context->x() ? visit(context->x()).as() : "") + + // This is called for each FASM line. + // Tags: comment (#), line (l) + virtual Any visitFasmLine(FasmParser::FasmLineContext *context) override { + std::ostringstream data; + data << GET(setFasmFeature) + << GET(annotations); + + if (context->COMMENT_CAP()) { + std::string c = context->COMMENT_CAP()->getText(); + c.erase(0, 1); // Remove the leading # + data << Str('#', c); + } + + if (!data.str().empty()) { + return withHeader('l', data.str()); + } else { + return std::string(); // Don't emit empty lines. + } + } + + // The set feature portion of a line (before annotations and comment.) + // Tags: feature (f), set feature (s) + virtual Any visitSetFasmFeature(FasmParser::SetFasmFeatureContext *context) override { + std::ostringstream data; + data << Str('f', context->FEATURE()->getText()) + << GET(featureAddress) + << GET(value); + return withHeader('s', data.str()); + } + + // The bracketed address, where the second number is optional. + // Tags: address (:) + virtual Any visitFeatureAddress(FasmParser::FeatureAddressContext *context) override { + std::ostringstream data; + data << Num(std::stoul(context->INT(0)->getText())); + + if (context->INT(1)) { + data << Num(std::stoul(context->INT(1)->getText())); + } + return withHeader(':', data.str()); + } + + // A Verilog style number. It can be "plain" (no leading size and base), + // or hex (h), binary (b), decimal (d), or octal (o). + virtual Any visitVerilogValue(FasmParser::VerilogValueContext *context) override { + std::string value = ""; + if (context->verilogDigits()) { + value = visit(context->verilogDigits()).as(); + } + return value; + } + + // A "plain" decimal value. + // Tags: plain (p) + virtual Any visitPlainDecimal(FasmParser::PlainDecimalContext *context) override { + std::ostringstream data; + data << Num('p', std::stol(context->INT()->getText())); + return data.str(); + } + + // A Verilog hex value. + // Tags: hex (h) + virtual Any visitHexValue(FasmParser::HexValueContext *context) override { + std::ostringstream data; + std::string value = context->HEXADECIMAL_VALUE()->getText(); + auto it = value.begin(); + it += 2; // skip 'h + + // Build up Nums 4 bits at a time, skipping '_'. + int bits = lead_bits(count_without(it, value.end(), '_') * 4); + uint32_t word = 0; + while (it != value.end()) { + if (*it != '_') { + word = (word << 4) | from_hex_digit(*it); + bits += 4; + if (bits == Num::width) { + data << Num(word); + word = 0; + bits = 0; + } + } + it++; + } + assert(!word); + return withHeader('h', data.str()); + } + + // A Verilog binary value. + // Tags: binary (b) + virtual Any visitBinaryValue(FasmParser::BinaryValueContext *context) override { + std::ostringstream data; + std::string value = context->BINARY_VALUE()->getText(); + auto it = value.begin(); + it += 2; // skip 'b + + // Build up Nums a bit at a time, skipping '_'. + int bits = lead_bits(count_without(it, value.end(), '_')); + uint32_t word = 0; + while (it != value.end()) { + if (*it != '_') { + word = (word << 1) | (*it == '1' ? 1 : 0); + bits += 1; + if (bits == Num::width) { + data << Num(word); + word = 0; + bits = 0; + } + } + it++; + } + assert(!word); + return withHeader('b', data.str()); + } + + // A Verilog decimal value. + // Tags: decimal (d) + virtual Any visitDecimalValue(FasmParser::DecimalValueContext *context) override { + long long integer = 0; + std::string value = context->DECIMAL_VALUE()->getText(); + auto it = value.begin(); + it += 2; // skip 'd + + // Build up a Num, skipping '_'. + while (it != value.end()) { + if (*it != '_') { + integer = (integer * 10) + (*it - '0'); + } + it++; + } + + std::ostringstream data; + data << Num('d', integer); + return data.str(); + } + + // A Verilog octal value. + // Tags: octal (o) + virtual Any visitOctalValue(FasmParser::OctalValueContext *context) override { + std::ostringstream data; + std::string value = context->OCTAL_VALUE()->getText(); + auto it = value.begin(); + it += 2; // skip 'b + + // Build up a Num 3 bits at a time. + // Note that since the word size is not evenly divisible by 3, + // intermediate values can be greater than the word size. + // This is why the 'word' below is 64 bits wide. + int bits = lead_bits(count_without(it, value.end(), '_') * 3); + uint64_t word = 0; // could temporarily overflow uint32_t + while (it != value.end()) { + if (*it != '_') { + word = (word << 3) | (*it - '0'); + bits += 3; + if (bits >= Num::width) { + data << Num(word >> (bits - Num::width)); + word >>= Num::width; + bits -= Num::width; + } + } + it++; + } + assert(!word); + return withHeader('o', data.str()); + } + + // A collection of annotations. { ... } + // Tags: annotations ({) + virtual Any visitAnnotations(FasmParser::AnnotationsContext *context) override { + std::ostringstream data; + for (auto &a : context->annotation()) { + data << visit(a).as(); + } + return withHeader('{', data.str()); + } + + // An annotation: x = "y" + // Tags: annotation (a), annotation name (.), annotation value (=) + virtual Any visitAnnotation(FasmParser::AnnotationContext *context) override { + std::ostringstream data; + data << Str('.', context->ANNOTATION_NAME()->getText()); + if (context->ANNOTATION_VALUE()) { + std::string value = context->ANNOTATION_VALUE()->getText(); + value.erase(0, 1); // Convert "value" -> value + value.pop_back(); + data << Str('=', value); + } + return withHeader('a', data.str()); + } + +private: + + std::ostream &out; +}; + + +// Common portion of 'from_string' and 'from_file'. +// Consumes an input stream and produces an output stream. +static +void parse_fasm(std::istream &in, std::ostream &out) { + ANTLRInputStream stream(in); + FasmLexer lexer(&stream); + CommonTokenStream tokens(&lexer); + FasmParser parser(&tokens); + auto *tree = parser.fasmFile(); + FasmParserBaseVisitor(out).visit(tree); +} + +// This static string is used to temporarily hold the result +// returned from external functions. +// This is to allow Python consumers, because I could not +// find a convenient way for Python to free a malloc'ed string. +static std::string result; + +// Parse the given input string, returning output. +// Use hex mode (see above) if hex is true. +const char *from_string(const char *in, bool hex) { + hex_mode = hex; + std::istringstream input(in); + std::ostringstream output; + parse_fasm(input, output); + output.put(0); + result = output.str(); + return result.c_str(); +} + +// Parse the given input file, returning output. +// Use hex mode (see above) if hex is true. +const char *from_file(const char *path, bool hex) { + hex_mode = hex; + std::fstream input(std::string(path), input.in); + std::ostringstream output; + if(!input.is_open()) return nullptr; + parse_fasm(input, output); + output.put(0); + result = output.str(); + return result.c_str(); +} + +// This can be used as a standalone utility. +// parse_fasm [file] [-hex] +// file : The file to parse, otherwise use stdin. +// -hex : Enable hex mode. +int main(int argc, char *argv[]) { + if (argc > 1) { + if (argc > 2 && std::string(argv[2]) == "-hex") { + hex_mode = true; + } + std::ifstream in; + in.open(argv[1]); + parse_fasm(in, std::cout); + } else { + hex_mode = true; + std::istringstream in_line; + for (std::string line; std::getline(std::cin, line);) { + in_line.str(line); + parse_fasm(in_line, std::cout); + } + } + return 0; +} diff --git a/fasm/FasmLexer.g4 b/fasm/FasmLexer.g4 new file mode 100644 index 00000000..62a553ab --- /dev/null +++ b/fasm/FasmLexer.g4 @@ -0,0 +1,30 @@ +lexer grammar FasmLexer; + +S : [ \t] -> skip ; +NEWLINE : [\n\r] ; +fragment NON_ESCAPE_CHARACTERS : ~[\\"] ; +fragment ESCAPE_SEQUENCES : [\\] [\\"] ; +fragment IDENTIFIER : [a-zA-Z] [0-9a-zA-Z_]* ; +FEATURE : IDENTIFIER ('.' IDENTIFIER)* ; +HEXADECIMAL_VALUE : '\'h' [ \t]* [0-9a-fA-F_]+ ; +BINARY_VALUE : '\'b' [ \t]* [01_]+ ; +DECIMAL_VALUE : '\'d' [ \t]* [0-9_]+ ; +OCTAL_VALUE : '\'o' [ \t]* [0-7_]+ ; +INT : [0-9]+ ; +COMMENT_CAP : '#' (~[\n\r])* ; + +EQUAL : '=' ; +OPEN_BRACKET : '[' ; +COLON : ':' ; +CLOSE_BRACKET : ']' ; + +BEGIN_ANNOTATION : '{' -> pushMode(ANNOTATION_MODE) ; + +mode ANNOTATION_MODE; +ANNOTATION_S : [ \t] -> skip ; +ANNOTATION_NAME : [.a-zA-Z] [0-9a-zA-Z_]* ; +ANNOTATION_VALUE : '"' (NON_ESCAPE_CHARACTERS | ESCAPE_SEQUENCES)* '"' ; +END_ANNOTATION : '}' -> popMode ; +ANNOTATION_EQUAL : '=' ; +ANNOTATION_COMMA : ',' ; + diff --git a/fasm/FasmParser.g4 b/fasm/FasmParser.g4 new file mode 100644 index 00000000..b1b6afdc --- /dev/null +++ b/fasm/FasmParser.g4 @@ -0,0 +1,24 @@ +parser grammar FasmParser; +options { tokenVocab=FasmLexer; } + +fasmFile : fasmLine (NEWLINE fasmLine)* EOF ; +fasmLine : setFasmFeature? + annotations? + COMMENT_CAP? + ; + +setFasmFeature : FEATURE featureAddress? (EQUAL value)? ; +featureAddress : '[' INT (':' INT)? ']' ; + +value : INT? verilogDigits # VerilogValue + | INT # PlainDecimal + ; + +verilogDigits : HEXADECIMAL_VALUE # HexValue + | BINARY_VALUE # BinaryValue + | DECIMAL_VALUE # DecimalValue + | OCTAL_VALUE # OctalValue + ; + +annotations : BEGIN_ANNOTATION annotation (',' annotation)* END_ANNOTATION ; +annotation : ANNOTATION_NAME ANNOTATION_EQUAL ANNOTATION_VALUE ; diff --git a/fasm/__init__.py b/fasm/__init__.py index cf5e1cf3..2e974168 100644 --- a/fasm/__init__.py +++ b/fasm/__init__.py @@ -10,12 +10,18 @@ # SPDX-License-Identifier: ISC from __future__ import print_function -import textx import os.path import argparse from collections import namedtuple import enum +from ctypes import * +import os +from sys import byteorder +here = os.path.dirname(os.path.realpath(__file__)) +parse_fasm = CDLL(os.path.join(here, "parse_fasm.so")) +parse_fasm.from_string.restype = POINTER(c_char) +parse_fasm.from_file.restype = POINTER(c_char) class ValueFormat(enum.Enum): PLAIN = 0 @@ -24,6 +30,14 @@ class ValueFormat(enum.Enum): VERILOG_BINARY = 3 VERILOG_OCTAL = 4 +# Converts tag values from parse_fasm to ValueFormats +tag_to_value_format = { + b'p' : ValueFormat.PLAIN, + b'd' : ValueFormat.VERILOG_DECIMAL, + b'h' : ValueFormat.VERILOG_HEX, + b'b' : ValueFormat.VERILOG_BINARY, + b'o' : ValueFormat.VERILOG_OCTAL +} # Python version of a SetFasmFeature line. # feature is a string @@ -46,122 +60,134 @@ class ValueFormat(enum.Enum): # comment should a string or None. FasmLine = namedtuple('FasmLine', 'set_feature annotations comment') - -def assert_max_width(width, value): - """ asserts if the value is greater than the width. """ - assert value < (2**width), (width, value) - - -def verilog_value_to_int(verilog_value): - """ Convert VerilogValue model to width, value, value_format """ - width = None - - if verilog_value.plain_decimal: - return width, int(verilog_value.plain_decimal), ValueFormat.PLAIN - - if verilog_value.width: - width = int(verilog_value.width) - - if verilog_value.hex_value: - value = int(verilog_value.hex_value.replace('_', ''), 16) - value_format = ValueFormat.VERILOG_HEX - elif verilog_value.binary_value: - value = int(verilog_value.binary_value.replace('_', ''), 2) - value_format = ValueFormat.VERILOG_BINARY - elif verilog_value.decimal_value: - value = int(verilog_value.decimal_value.replace('_', ''), 10) - value_format = ValueFormat.VERILOG_DECIMAL - elif verilog_value.octal_value: - value = int(verilog_value.octal_value.replace('_', ''), 8) - value_format = ValueFormat.VERILOG_OCTAL +# The following functions decode the binary format generated by parse_fasm. +# This is a lightweight binary format designed to be fast to decode. + +# Match a tag and retrieve length from the header. +def get_header(tags, data, i): + if not data[i] in bytes(tags, 'ascii'): + # print('tag mismatch: got {}, expecting {} at position {}'.format(data[i], tags, i)) + # print('data: {}', data[:i]) + return None, i + + return int.from_bytes(data[i+1:i+5], byteorder), i+5 + +# Decode a tagged string +def tagged_string_from_bytes(tag, data, i): + length, i = get_header(tag, data, i) + if length is None: + return None, i + + return data[i:i+length].decode('ascii'), i+length + +# Decode a FASM address: [x:y] +def fasm_address_from_bytes(data, i): + length, i = get_header(':', data, i) + if not length: + return None, None, i + + end = int.from_bytes(data[i:i+4], byteorder) + start = int.from_bytes(data[i+4:i+8], byteorder) if length == 8 else None + return start, end, i+length + +# Decode a FASM value +def fasm_value_from_bytes(data, i): + tag = data[i] + if tag == b'p': + length = 4 + i = i + 1 else: - assert False, verilog_value - - if width is not None: - assert_max_width(width, value) - - return width, value, value_format - - -def set_feature_model_to_tuple(set_feature_model): - start = None - end = None - value = 1 - address_width = 1 - value_format = None + length, i = get_header('hobd', data, i) - if set_feature_model.feature_address: - if set_feature_model.feature_address.address2: - end = int(set_feature_model.feature_address.address1, 10) - start = int(set_feature_model.feature_address.address2, 10) - address_width = end - start + 1 - else: - start = int(set_feature_model.feature_address.address1, 10) - end = None - address_width = 1 + if not length: + return None, None, i + + return int.from_bytes(data[i:i+length], byteorder), tag_to_value_format[tag], i+length - if set_feature_model.feature_value: - width, value, value_format = verilog_value_to_int( - set_feature_model.feature_value) +# Decode a set feature: feature = value +def fasm_set_feature_from_bytes(data, i): + length, i = get_header('s', data, i) + if not length: + return None, i - if width is not None: - assert width <= address_width + feature, p = tagged_string_from_bytes('f', data, i) + start, end, p = fasm_address_from_bytes(data, p) + value, value_format, p = fasm_value_from_bytes(data, p) - assert value < (2**address_width), (value, address_width) + if not value: + value = 1 + if end and not start: + start = end + return SetFasmFeature( - feature=set_feature_model.feature, - start=start, - end=end, - value=value, - value_format=value_format, - ) - - -def get_fasm_metamodel(): - return textx.metamodel_from_file( - file_name=os.path.join(os.path.dirname(__file__), 'fasm.tx'), - skipws=False) - - -def fasm_model_to_tuple(fasm_model): - """ Converts FasmFile model to list of FasmLine named tuples. """ - if not fasm_model: - return - - for fasm_line in fasm_model.lines: - set_feature = None - annotations = None - comment = None - - if fasm_line.set_feature: - set_feature = set_feature_model_to_tuple(fasm_line.set_feature) - - if fasm_line.annotations: - annotations = tuple( - Annotation( - name=annotation.name, - value=annotation.value if annotation.value else '') - for annotation in fasm_line.annotations.annotations) - - if fasm_line.comment: - comment = fasm_line.comment.comment - - yield FasmLine( - set_feature=set_feature, - annotations=annotations, - comment=comment, - ) - + feature = feature, + start = start, + end = end, + value = value, + value_format = value_format + ), i+length + +# Decode an annotation: x = "y" +def fasm_annotation_from_bytes(data, i): + length, i = get_header('a', data, i) + if not length: + return None, i + + name, p = tagged_string_from_bytes('.', data, i) + value, p = tagged_string_from_bytes('=', data, p) + return Annotation( + name = name, + value = value + ), i+length + +# Decode a set of annotations: { ... } +def fasm_annotations_from_bytes(data, i): + length, i = get_header('{', data, i) + if not length: + return None, i + + annotations = [] + annotation, p = fasm_annotation_from_bytes(data, i) + while annotation: + annotations.append(annotation) + annotation, p = fasm_annotation_from_bytes(data, p) + + return annotations, i+length + +# Decode an entire FASM line +def fasm_line_from_bytes(data, i): + length, i = get_header('l', data, i) + if not length: + return None, i + + set_feature, p = fasm_set_feature_from_bytes(data, i) + annotations, p = fasm_annotations_from_bytes(data, p) + comment, p = tagged_string_from_bytes('#', data, p) + + return FasmLine( + set_feature = set_feature, + annotations = annotations, + comment = comment + ), i+length + + +def parse_fasm_data(data): + """ Parse FASM string, returning list of FasmLine named tuples.""" + lines = [] + line, p = fasm_line_from_bytes(data, 0) + while line: + lines.append(line) + line, p = fasm_line_from_bytes(data, p) + return lines def parse_fasm_string(s): """ Parse FASM string, returning list of FasmLine named tuples.""" - return fasm_model_to_tuple(get_fasm_metamodel().model_from_str(s)) - + return parse_fasm_data(parse_fasm.from_string(bytes(s, 'ascii'), 0)) def parse_fasm_filename(filename): """ Parse FASM file, returning list of FasmLine named tuples.""" - return fasm_model_to_tuple(get_fasm_metamodel().model_from_file(filename)) + return parse_fasm_data(parse_fasm.from_file(bytes(filename, 'ascii'), 0)) def fasm_value_to_str(value, width, value_format): @@ -291,6 +317,75 @@ def canonical_features(set_feature): value_format=None, ) +#class FasmTupleVisitor(FasmParserVisitor): +# def visitFasmFile(self, ctx): +# for c in ctx.fasmLine(): +# line = self.visit(c); +# if line.set_feature or line.annotations or line.comment: +# yield line +# +# def visitFasmLine(self, ctx): +# return FasmLine( +# set_feature = self.visit(ctx.setFasmFeature()) if ctx.setFasmFeature() else None, +# annotations = list(self.visit(ctx.annotations())) if ctx.annotations() else None, +# comment = ctx.COMMENT_CAP().getText()[1:] if ctx.COMMENT_CAP() else None +# ) +# +# def visitSetFasmFeature(self, ctx): +# start = None +# end = None +# value = 1 +# value_format = None +# +# if ctx.value(): +# value_format, value = self.visit(ctx.value()) +# +# if ctx.featureAddress(): +# start, end = self.visit(ctx.featureAddress()) +# +# return SetFasmFeature( +# feature=ctx.FEATURE().getText(), +# start=start, +# end=end, +# value=value, +# value_format=value_format +# ) +# +# def visitFeatureAddress(self, ctx): +# end = int(ctx.INT(0).getText(), 10) +# start = int(ctx.INT(1).getText(), 10) if ctx.INT(1) else end +# return start, end +# +# def visitVerilogValue(self, ctx): +# value_format, value = self.visit(ctx.verilogDigits()) +# if True: +# assert value < 2 ** int(ctx.INT().getText()), "value larger than bit width" +# return value_format, value +# +# def visitHexValue(self, ctx): +# return ValueFormat.VERILOG_HEX, int(ctx.HEXADECIMAL_VALUE().getText()[2:].replace('_', ''), 16); +# +# def visitBinaryValue(self, ctx): +# return ValueFormat.VERILOG_BINARY, int(ctx.BINARY_VALUE().getText()[2:].replace('_', ''), 2); +# +# def visitDecimalValue(self, ctx): +# return ValueFormat.VERILOG_DECIMAL, int(ctx.DECIMAL_VALUE().getText()[2:].replace('_', ''), 10); +# +# def visitOctalValue(self, ctx): +# return ValueFormat.VERILOG_OCTAL, int(ctx.OCTAL_VALUE().getText()[2:].replace('_', ''), 8); +# +# def visitPlainDecimal(self, ctx): +# return ValueFormat.PLAIN, int(ctx.INT().getText(), 10); +# +# def visitAnnotations(self, ctx): +# return map(self.visit, ctx.annotation()) +# +# def visitAnnotation(self, ctx): +# return Annotation( +# name=ctx.ANNOTATION_NAME().getText(), +# value='' if not ctx.ANNOTATION_NAME() else ctx.ANNOTATION_VALUE().getText()[1:-1] +# ) + def fasm_line_to_string(fasm_line, canonical=False): if canonical: diff --git a/setup.cfg b/setup.cfg index f7700bab..cbda7cdf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,8 @@ license_files = COPYING [bdist_wheel] universal=1 + +[antlr] +visitor = yes +x_exact_output_dir = yes +output = default=fasm diff --git a/setup.py b/setup.py index 7a9c5a56..4bb241f4 100644 --- a/setup.py +++ b/setup.py @@ -10,13 +10,22 @@ # SPDX-License-Identifier: ISC import setuptools +import setuptools.command.build_py +import os with open("README.md", "r") as fh: long_description = fh.read() +class BuildMakeCommand(setuptools.command.build_py.build_py): + """Run make.""" + + def run(self): + os.system('make') + setuptools.command.build_py.build_py.run(self) + setuptools.setup( name="fasm", - version="0.0.1", + version="0.0.2", author="SymbiFlow Authors", author_email="symbiflow@lists.librecores.org", description="FPGA Assembly (FASM) Parser and Generation library", @@ -25,15 +34,18 @@ url="https://github.com/SymbiFlow/fasm", packages=setuptools.find_packages(exclude=('tests*',)), package_data={ - 'fasm': ['*.tx'], + 'fasm': ['*.g4'], }, - install_requires=['textx'], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: ISC License", "Operating System :: OS Independent", ], entry_points={ + 'scripts' : 'parse_fasm', 'console_scripts': ['fasm=fasm:main'], }, + cmdclass={ + 'build_py' : BuildMakeCommand, + }, )