Initial warning support
This commit is contained in:
parent
61aee1c0c4
commit
a3b4fd1048
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
33
src/main.nim
33
src/main.nim
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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) =
|
||||
|
|
Loading…
Reference in New Issue