Initial warning support

This commit is contained in:
Mattia Giambirtone 2022-11-22 15:13:42 +01:00
parent 61aee1c0c4
commit a3b4fd1048
4 changed files with 78 additions and 33 deletions

View File

@ -29,6 +29,7 @@ import std/strutils
import std/sequtils
import std/sets
import std/os
import std/terminal
export ast
@ -89,6 +90,9 @@ export bytecode
type
WarningKind* {.pure.} = enum
## A warning enumeration type
UnreachableCode, UnusedName,
NameKind {.pure.} = enum
## A name enumeration type
None, Module, Argument, Var, Function, CustomType, Enum
@ -216,6 +220,8 @@ type
# declarations so that we can patch them
# later
forwarded: seq[tuple[name: Name, pos: int]]
# List of disabled warnings
disabledWarnings: seq[WarningKind]
CompileError* = ref object of PeonException
compiler*: Compiler
node*: ASTNode
@ -225,7 +231,7 @@ type
# Forward declarations
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
incremental: bool = false, isMainModule: bool = true): Chunk
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[]): Chunk
proc expression(self: Compiler, node: Expression)
proc statement(self: Compiler, node: Statement)
proc declaration(self: Compiler, node: Declaration)
@ -237,7 +243,7 @@ proc matchImpl(self: Compiler, name: string, kind: Type, node: ASTNode = nil, al
proc infer(self: Compiler, node: LiteralExpr, allowGeneric: bool = false): Type
proc infer(self: Compiler, node: Expression, allowGeneric: bool = false): Type
proc inferOrError[T: LiteralExpr | Expression](self: Compiler, node: T, allowGeneric: bool = false): Type
proc findByName(self: Compiler, name: string): seq[Name]
proc findByName(self: Compiler, name: string, resolve: bool = true): seq[Name]
proc findByModule(self: Compiler, name: string): seq[Name]
proc findByType(self: Compiler, name: string, kind: Type, depth: int = -1): seq[Name]
proc compare(self: Compiler, a, b: Type): bool
@ -276,6 +282,7 @@ proc newCompiler*(replMode: bool = false): Compiler =
result.isMainModule = false
result.closures = @[]
result.forwarded = @[]
result.disabledWarnings = @[]
## Public getters for nicer error formatting
@ -317,6 +324,21 @@ proc error(self: Compiler, message: string, node: ASTNode = nil) {.raises: [Comp
raise CompileError(msg: message, node: if node.isNil(): self.getCurrentNode() else: node, file: self.file, module: self.currentModule, compiler: self)
proc warning(self: Compiler, kind: WarningKind, message: string, node: ASTNode = nil) =
## Raises a warning
let node = if node.isNil(): self.getCurrentNode() else: node
let fn = self.getCurrentFunction()
var msg = &" (raised at line {node.token.line} in module '{self.currentModule}'"
if not fn.isNil() and fn.kind != lambdaExpr:
msg &= &", in function '{FunDecl(fn).name.token.lexeme}'"
msg &= ")"
if kind notin self.disabledWarnings:
stderr.styledWrite(fgYellow, &"Warning: ", fgCyan, "line ", fgRed, $node.token.line, fgCyan, ", module ", fgRed, &"'{self.currentModule}'")
if not fn.isNil() and fn.kind != lambdaExpr:
stderr.styledWrite(fgCyan, ", function ", fgRed, &"'{FunDecl(fn).name.token.lexeme}'")
stderr.styledWriteLine(fgCyan, " -> ", fgYellow, message)
proc step(self: Compiler): ASTNode {.inline.} =
## Steps to the next node and returns
## the consumed one
@ -942,7 +964,7 @@ proc typeToStr(self: Compiler, typ: Type): string =
discard
proc findByName(self: Compiler, name: string): seq[Name] =
proc findByName(self: Compiler, name: string, resolve: bool = true): seq[Name] =
## Looks for objects that have been already declared
## with the given name. Returns all objects that apply.
## As with resolve(), this will cause type and function
@ -953,23 +975,25 @@ proc findByName(self: Compiler, name: string): seq[Name] =
if obj.isPrivate or not obj.exported:
continue
result.add(obj)
for n in result:
if n.resolved:
continue
n.resolved = true
case n.kind:
of NameKind.CustomType:
self.typeDecl(TypeDecl(n.node), n)
of NameKind.Function:
if not n.valueType.isGeneric:
self.funDecl(FunDecl(n.node), n)
else:
discard
if resolve:
for n in result:
if n.resolved:
continue
n.resolved = true
case n.kind:
of NameKind.CustomType:
self.typeDecl(TypeDecl(n.node), n)
of NameKind.Function:
if not n.valueType.isGeneric:
self.funDecl(FunDecl(n.node), n)
else:
discard
proc findByModule(self: Compiler, name: string): seq[Name] =
## Looks for objects that have been already declared AS
## public within the given module. Returns all objects that apply
## Looks for objects that have been already declared as
## public within the given module. Returns all objects
## that apply
for obj in reversed(self.names):
if not obj.isPrivate and obj.owner == name:
result.add(obj)
@ -1188,9 +1212,9 @@ proc endScope(self: Compiler) =
for name in self.names:
if name.depth > self.depth:
names.add(name)
#[if not name.resolved:
# TODO: Emit a warning?
continue]#
if not name.resolved and not self.replMode:
self.warning(UnusedName, &"'{name.ident.token.lexeme}' is declared but not used")
#continue
if name.owner != self.currentModule and self.depth > -1:
# Names coming from other modules only go out of scope
# when the global scope is closed (i.e. at the end of
@ -1384,7 +1408,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false) =
else:
discard # TODO: Types, enums
self.dispatchPragmas(n)
for name in self.findByName(declaredName):
for name in self.findByName(declaredName, resolve=false):
if name == n:
continue
elif (name.kind == NameKind.Var and name.depth == self.depth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]:
@ -1509,6 +1533,7 @@ proc beginProgram(self: Compiler): int =
codePos: self.chunk.code.len() + 12,
ident: newIdentExpr(Token(lexeme: "", kind: Identifier)),
kind: NameKind.Function,
resolved: true,
line: -1)
self.names.add(main)
self.scopeOwners.add((main, 0))
@ -2321,7 +2346,7 @@ proc declaration(self: Compiler, node: Declaration) =
proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tuple[start, stop: int]], source: string, chunk: Chunk = nil,
incremental: bool = false, isMainModule: bool = true): Chunk =
incremental: bool = false, isMainModule: bool = true, disabledWarnings: seq[WarningKind] = @[]): Chunk =
## Compiles a sequence of AST nodes into a chunk
## object
if chunk.isNil():
@ -2337,6 +2362,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu
self.lines = lines
self.source = source
self.isMainModule = isMainModule
self.disabledWarnings = disabledWarnings
if not incremental:
self.jumps = @[]
let pos = self.beginProgram()
@ -2375,7 +2401,7 @@ proc compileModule(self: Compiler, moduleName: string) =
path, self.lexer.getLines(),
source, persist=true),
path, self.lexer.getLines(), source, chunk=self.chunk, incremental=true,
isMainModule=false)
isMainModule=false, self.disabledWarnings)
self.depth = 0
self.current = current
self.ast = ast

View File

@ -1114,7 +1114,7 @@ proc expressionStatement(self: Parser): Statement =
## Parses expression statements, which
## are expressions followed by a semicolon
var expression = self.expression()
endOfLine("missing expression terminator", expression.token)
endOfLine("missing semicolon at end of expression", expression.token)
result = Statement(newExprStmt(expression, expression.token))

View File

@ -186,7 +186,8 @@ proc repl =
quit(0)
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false) =
proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints: seq[uint64] = @[], dis: bool = false,
warnings: seq[WarningKind] = @[]) =
var
tokens: seq[Token] = @[]
tree: seq[Declaration] = @[]
@ -231,7 +232,7 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
for node in tree:
styledEcho fgGreen, "\t", $node
echo ""
compiled = compiler.compile(tree, f, tokenizer.getLines(), input)
compiled = compiler.compile(tree, f, tokenizer.getLines(), input, disabledWarnings=warnings)
when debugCompiler:
styledEcho fgCyan, "Compilation step:\n"
debugger.disassembleChunk(compiled, f)
@ -297,7 +298,6 @@ proc runFile(f: string, fromString: bool = false, dump: bool = true, breakpoints
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ") + line.find(lexeme)) & "^".repeat(abs(relPos.stop - relPos.start - line.find(lexeme)))
except CompileError:
raise
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
var lineNo = exc.node.token.line
@ -328,6 +328,7 @@ when isMainModule:
var file: string = ""
var fromString: bool = false
var dump: bool = true
var warnings: seq[WarningKind] = @[]
var breaks: seq[uint64] = @[]
var dis: bool = false
for kind, key, value in optParser.getopt():
@ -347,6 +348,24 @@ when isMainModule:
fromString = true
of "no-dump":
dump = false
of "warnings":
if value.toLowerAscii() in ["yes", "on"]:
warnings = @[]
elif value.toLowerAscii() in ["no", "off"]:
for warning in WarningKind:
warnings.add(warning)
else:
stderr.writeLine("error: invalid value for option 'warnings' (valid options: yes, on, no, off)")
quit()
of "no-warn":
case value:
of "unusedVariable":
warnings.add(WarningKind.UnusedName)
of "unreachableCode":
warnings.add(WarningKind.UnreachableCode)
else:
stderr.writeLine("error: invalid warning name for option 'warnings'")
quit()
of "breakpoints":
when not debugVM:
echo "error: cannot set breakpoints in release mode"
@ -377,18 +396,18 @@ when isMainModule:
dump = false
of "b":
when not debugVM:
echo "error: cannot set breakpoints in release mode"
stderr.writeLine("error: cannot set breakpoints in release mode")
quit()
for point in value.strip(chars={' '}).split(","):
try:
breaks.add(parseBiggestUInt(point))
except ValueError:
echo &"error: invalid breakpoint value '{point}'"
stderr.writeLine(&"error: invalid breakpoint value '{point}'")
quit()
of "d":
dis = true
else:
echo &"error: unkown option '{key}'"
stderr.writeLine(&"error: unkown option '{key}'")
quit()
else:
echo "usage: peon [options] [filename.pn]"
@ -396,7 +415,7 @@ when isMainModule:
if file == "":
repl()
else:
runFile(file, fromString, dump, breaks, dis)
runFile(file, fromString, dump, breaks, dis, warnings)

View File

@ -68,7 +68,7 @@ proc checkFrameStart(self: Debugger, n: int) =
for i, e in self.cfiData:
if n == e.start and not (e.started or e.stopped):
e.started = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Function Start ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
@ -80,7 +80,7 @@ proc checkFrameEnd(self: Debugger, n: int) =
for i, e in self.cfiData:
if n == e.stop and e.started and not e.stopped:
e.stopped = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Function End ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
proc simpleInstruction(self: Debugger, instruction: OpCode) =