Minor changes to help text. Add AST checker module

This commit is contained in:
Mattia Giambirtone 2024-04-05 16:39:05 +02:00
parent 3953751cef
commit 740999bbc2
2 changed files with 100 additions and 6 deletions

View File

@ -74,7 +74,7 @@ Options
--noParse Don't parse the code (i.e. stop at the tokenization stage)
--noTC, --noTypeCheck Don't typecheck the code (i.e. stop at the parsing stage)
--showMismatches Show all mismatches when function dispatching fails (output is really verbose)
--showMismatches Show all mismatches when type dispatching fails (output is really verbose)
--debugLexer Show the lexer's output
--debugParser Show the parser's output
--debugTypeChecker Show the typechecker's output
@ -82,17 +82,17 @@ Options
--listWarns Show a list of all warnings
-b, --backend Select the compilation backend. Currently only supports 'bytecode' (the default)
-c, --compile Compile the code, but do not run the main module
-o, --output Rename the output executable to this (a "bc" extension is added for bytecode files,
-o, --output Rename the output executable to this (a "pbc" extension is added for bytecode files,
if not already present)
-s, --string Run the given string as if it were a file (the filename is set to '<string>')
--cacheDir Specify a directory where the peon compiler will dump code generation results
to speed up subsequent builds. Defaults to ".buildcache"
The following options are specific to the 'bytecode' backend:
-n, --noDump Do not dump bytecode files to the source directory. Note that
no files are dumped when using -s/--string
--breakpoints Set debugging breakpoints at the given bytecode offsets.
-n, --noDump Do not dump bytecode files to the source directory. Redundant
when using -s/--string
--breakpoints Pause execution and invoke the debugger at the given bytecode offsets.
Input should be a comma-separated list of positive integers
(spacing is irrelevant). Only works if peon was compiled with
-d:debugVM
VM debugging enabled
"""

94
src/util/astchecker.nim Normal file
View File

@ -0,0 +1,94 @@
import frontend/parsing/parser
import frontend/parsing/lexer
import std/strformat
import std/sequtils
import std/strutils
type
Comparison* = ref object
# The result of a comparison between
# AST nodes
match*: bool # Did the comparison succeed?
reason*: string # If it failed, why?
exc*: ref Exception # If the comparison crashed with an error, this is the exception
inner*: Comparison # A comparison could fail because another, inner comparison failed
nodes*: tuple[provided, expected: ASTNode]
ASTChecker* = ref object
## An AST validator. Ensures the
## output of the parser matches
## the structure we expect
proc newASTChecker*: ASTChecker =
## Initializes a new, blank AST checker
new(result)
template ensureEqualType(self, other: ASTNode) = doAssert self.kind == other.kind, "cannot compare AST nodes of different types"
proc newComparison(match: bool, expected, provided: ASTNode, reason: string = "", exc: ref Exception = nil, inner: Comparison = nil): Comparison =
## Initializes a new comparison object
new(result)
result.match = match
result.nodes.expected = expected
result.nodes.provided = provided
result.reason = reason
result.exc = exc
result.inner = inner
proc compare(provided, expected: LiteralExpr): Comparison =
## Compares two literal expressions
## for structural equality
ensureEqualType(provided, expected)
result = newComparison(true, expected, provided)
case provided.kind:
of infExpr, nanExpr, falseExpr, trueExpr:
return
of strExpr, intExpr, hexExpr, octExpr, binExpr:
if provided.literal.kind != expected.literal.kind:
result.match = false
result.reason = &"expected token of type {expected.literal.kind}, got {provided.literal.kind}"
elif provided.literal.lexeme != expected.literal.lexeme:
result.match = false
result.reason = &"expected lexeme to be {expected.literal.lexeme.escape()}, got {provided.literal.lexeme.escape()}"
else:
raise newException(ValueError, &"literal comparison attempted with non-literal node of type {provided.kind}")
proc compare(provided, expected: ASTNode): Comparison =
## Compares two generic AST nodes for
## structural equality
ensureEqualType(provided, expected)
if provided.isLiteral():
return compare(LiteralExpr(provided), LiteralExpr(expected))
proc check*(self: ASTChecker, code: string, structure: ParseTree): Comparison =
## Ensures the given source code parses into the given structure
try:
result = newComparison(true, nil, nil)
var
tokenizer = newLexer()
parser = newParser()
tokens = tokenizer.lex(code, "test")
tree = parser.parse(tokens, "test", tokenizer.getLines(), tokenizer.getSource())
if len(tree) != len(structure):
result.match = false
result.reason = &"Number of provided nodes ({structure.len()}) does not match number of returned nodes ({tree.len()})"
for (provided, expected) in zip(tree, structure):
let cmp = compare(provided, expected)
if not cmp.match:
result.inner = cmp
result.reason = "Comparison failed, see the inner attribute for more details"
break
except LexingError:
return Comparison(match: false, reason: "Tokenization failed", exc: getCurrentException())
except ParseError:
return Comparison(match: false, reason: "Parsing failed", exc: getCurrentException())