This commit is contained in:
Irmen de Jong 2018-01-07 23:45:42 +01:00
parent 68c1d2af4c
commit 12c3ddd207
18 changed files with 87 additions and 52 deletions

View File

@ -1,6 +1 @@
"""
Programming Language for 6502/6510 microprocessors
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
# package

View File

@ -3,26 +3,42 @@ import sys
import linecache
from typing import Optional, Generator, Tuple, Set
from .plyparser import parse_file, Module, Directive, Block, Subroutine, AstNode
from .parse import ParseError
from .symbols import SourceRef
from .plylexer import SourceRef
class ParseError(Exception):
def __init__(self, message: str, sourcetext: Optional[str], sourceref: SourceRef) -> None:
self.sourceref = sourceref
self.msg = message
self.sourcetext = sourcetext
def __str__(self):
return "{} {:s}".format(self.sourceref, self.msg)
class PlyParser:
def __init__(self):
def __init__(self, parsing_import: bool=False) -> None:
self.parse_errors = 0
self.parsing_import = False
self.parsing_import = parsing_import
def parse_file(self, filename: str) -> Module:
print("parsing:", filename)
module = parse_file(filename)
module = parse_file(filename, self.lexer_error)
try:
self.check_directives(module)
self.remove_empty_blocks(module)
self.process_imports(module)
except ParseError as x:
self.handle_parse_error(x)
if self.parse_errors:
self.print_bold("\nNo output; there were {:d} errors.\n".format(self.parse_errors))
raise SystemExit(1)
return module
def lexer_error(self, sourceref: SourceRef, fmtstring: str, *args: str) -> None:
self.parse_errors += 1
self.print_bold("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
def remove_empty_blocks(self, module: Module) -> None:
# remove blocks without name and without address, or that are empty
for scope, parent in self.recurse_scopes(module):
@ -87,27 +103,39 @@ class PlyParser:
filename = self.find_import_file(arg, directive.sourceref.file)
if not filename:
raise ParseError("imported file not found", None, directive.sourceref)
imported_module = self.import_file(filename)
imported_module, import_parse_errors = self.import_file(filename)
imported_module.scope.parent_scope = module.scope
imported.append(imported_module)
self.parse_errors += import_parse_errors
if not self.parsing_import:
# compiler support library is always imported (in main parser)
filename = self.find_import_file("il65lib", module.sourceref.file)
if filename:
imported_module, import_parse_errors = self.import_file(filename)
imported_module.scope.parent_scope = module.scope
imported.append(imported_module)
self.parse_errors += import_parse_errors
else:
raise FileNotFoundError("missing il65lib")
# append the imported module's contents (blocks) at the end of the current module
for imported_module in imported:
for block in imported_module.scope.filter_nodes(Block):
module.scope.nodes.append(block)
def import_file(self, filename: str) -> Module:
sub_parser = PlyParser()
return sub_parser.parse_file(filename)
def import_file(self, filename: str) -> Tuple[Module, int]:
sub_parser = PlyParser(parsing_import=True)
return sub_parser.parse_file(filename), sub_parser.parse_errors
def find_import_file(self, modulename: str, sourcefile: str) -> Optional[str]:
candidates = [modulename+".ill", modulename]
filename_at_source_location = os.path.join(os.path.split(sourcefile)[0], modulename)
filename_at_libs_location = os.path.join(os.getcwd(), "lib", modulename)
candidates = [modulename,
filename_at_source_location,
filename_at_libs_location,
modulename+".ill",
filename_at_source_location+".ill",
filename_at_libs_location+".ill"]
if filename_at_source_location not in candidates:
candidates.append(filename_at_source_location+".ill")
candidates.append(filename_at_source_location)
filename_at_libs_location = os.path.join(os.path.split(__file__)[0], "lib", modulename)
if filename_at_libs_location not in candidates:
candidates.append(filename_at_libs_location+".ill")
candidates.append(filename_at_libs_location)
for filename in candidates:
if os.path.isfile(filename):
return filename

View File

@ -0,0 +1 @@
# package

View File

@ -1,6 +1,6 @@
"""
Programming Language for 6502/6510 microprocessors
This is the parser of the IL65 code, that generates a parse tree.
This is the hand-written parser of the IL65 code, that generates a parse tree.
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
@ -10,9 +10,7 @@ import re
import os
import sys
import shutil
import attr
from collections import defaultdict
from typing import Set, List, Tuple, Optional, Dict, Union, Generator
from .exprparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\
parse_expr_as_string, parse_arguments, parse_expr_as_comparison
from .symbols import *
@ -513,14 +511,15 @@ class Parser:
self._parse_import_file(filename)
def _parse_import_file(self, filename: str) -> None:
candidates = [filename+".ill", filename]
filename_at_source_location = os.path.join(os.path.split(self.sourceref.file)[0], filename)
filename_at_libs_location = os.path.join(os.getcwd(), "lib", filename)
candidates = [filename,
filename_at_source_location,
filename_at_libs_location,
filename+".ill",
filename_at_source_location+".ill",
filename_at_libs_location+".ill"]
if filename_at_source_location not in candidates:
candidates.append(filename_at_source_location+".ill")
candidates.append(filename_at_source_location)
filename_at_libs_location = os.path.join(os.path.split(__file__)[0], "../lib", filename)
if filename_at_libs_location not in candidates:
candidates.append(filename_at_libs_location+".ill")
candidates.append(filename_at_libs_location)
for filename in candidates:
if os.path.isfile(filename):
if not self.check_import_okay(filename):

View File

@ -12,10 +12,10 @@ import time
import os
import argparse
import subprocess
from .parse import Parser
from .optimize import Optimizer
from .preprocess import PreprocessingParser
from .codegen import CodeGenerator, Assembler64Tass
from .handwritten.parse import Parser
from .handwritten.optimize import Optimizer
from .handwritten.preprocess import PreprocessingParser
from .handwritten.codegen import CodeGenerator, Assembler64Tass
def main() -> None:

View File

@ -8,8 +8,21 @@ License: GNU GPL 3.0, see LICENSE
import sys
import ply.lex
from .symbols import SourceRef
from .parse import ParseError
import attr
@attr.s(slots=True, frozen=True)
class SourceRef:
file = attr.ib(type=str)
line = attr.ib(type=int)
column = attr.ib(type=int, default=0)
def __str__(self) -> str:
if self.column:
return "{:s}:{:d}:{:d}".format(self.file, self.line, self.column)
if self.line:
return "{:s}:{:d}".format(self.file, self.line)
return self.file
# token names
@ -294,7 +307,10 @@ def t_error(t):
line, col = t.lineno, find_tok_column(t)
filename = getattr(t.lexer, "source_filename", "<unknown-file>")
sref = SourceRef(filename, line, col)
t.lexer.error_function("{}: Illegal character '{:s}'", sref, t.value[0], sourceref=sref)
if hasattr(t.lexer, "error_function"):
t.lexer.error_function(sref, "illegal character '{:s}'", t.value[0])
else:
print("{}: illegal character '{:s}'".format(sref, t.value[0]), file=sys.stderr)
t.lexer.skip(1)
@ -304,12 +320,7 @@ def find_tok_column(token):
return token.lexpos - last_cr
def error_function(fmtstring, *args, sourceref: SourceRef=None) -> None:
raise ParseError(fmtstring.format(*args), None, sourceref=sourceref)
lexer = ply.lex.lex()
lexer.error_function = error_function # can override this
if __name__ == "__main__":

View File

@ -8,9 +8,8 @@ License: GNU GPL 3.0, see LICENSE
import attr
from ply.yacc import yacc
from typing import Union, Type, Generator
from .symbols import SourceRef
from .lexer import tokens, lexer, find_tok_column # get the lexer tokens. required.
from typing import Union, Generator
from .plylexer import SourceRef, tokens, lexer, find_tok_column
start = "start"
@ -800,9 +799,9 @@ def p_error(p):
print('\n[ERROR DEBUG: parser state={:d} stack: {} . {} ]'.format(parser.state, stack_state_str, p))
if p:
sref = SourceRef(p.lexer.source_filename, p.lineno, find_tok_column(p))
p.lexer.error_function("syntax error before '{:.20s}'", str(p.value), sourceref=sref)
p.lexer.error_function(sref, "syntax error before '{:.20s}'", str(p.value))
else:
lexer.error_function("syntax error at end of input", lexer.source_filename, sourceref=None)
lexer.error_function(None, "syntax error at end of input", lexer.source_filename)
def _token_sref(p, token_idx):
@ -840,7 +839,8 @@ class TokenFilter:
parser = yacc(write_tables=True)
def parse_file(filename: str) -> Module:
def parse_file(filename: str, lexer_error_func=None) -> Module:
lexer.error_function = lexer_error_func
lexer.lineno = 1
lexer.source_filename = filename
tfilter = TokenFilter(lexer)

View File

@ -1,5 +1,5 @@
import pytest
from il65 import codegen, symbols
from il65.handwritten import symbols, codegen
def test_float_to_mflpt5():

View File

@ -1,5 +1,5 @@
import pytest
from il65.symbols import Zeropage, SymbolError, DataType
from il65.handwritten.symbols import Zeropage, SymbolError, DataType
def test_zp_configure_onlyonce():

View File

@ -172,6 +172,7 @@ loop :
sub sub1 () -> () {
%breakpoint
%saveregisters off
%breakpoint