diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 22dd47d..12008ee 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -629,7 +629,6 @@ proc detectClosureVariable(self: Compiler, name: IdentExpr, depth: int = self.sc proc identifier(self: Compiler, node: IdentExpr) = ## Compiles access to identifiers let s = self.resolve(node) - echo s[] if s == nil: self.error(&"reference to undeclared name '{node.token.lexeme}'") elif s.isConst: @@ -920,7 +919,10 @@ proc inferDeclType(self: Compiler, node: Declaration): ASTNode = proc expression(self: Compiler, node: ASTNode) = ## Compiles all expressions if self.inferExprType(node) == nil: - self.error("expression has no type") + if node.kind != identExpr: + # So we can raise a more appropriate + # error in self.identifier() + self.error("expression has no type") case node.kind: of getItemExpr: discard # TODO diff --git a/src/frontend/optimizer.nim b/src/frontend/optimizer.nim deleted file mode 100644 index 74b143c..0000000 --- a/src/frontend/optimizer.nim +++ /dev/null @@ -1,384 +0,0 @@ -# Copyright 2022 Mattia Giambirtone & All Contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import meta/ast -import meta/token - -import parseutils -import strformat -import strutils -import math - - -type - WarningKind* = enum - unreachableCode, - nameShadowing, - isWithALiteral, - equalityWithSingleton, - valueOverflow, - implicitConversion, - invalidOperation - - Warning* = ref object - kind*: WarningKind - node*: ASTNode - - Optimizer* = ref object - warnings: seq[Warning] - foldConstants*: bool - - -proc newOptimizer*(foldConstants: bool = true): Optimizer = - ## Initializes a new optimizer object - new(result) - result.foldConstants = foldConstants - result.warnings = @[] - - -proc newWarning(self: Optimizer, kind: WarningKind, node: ASTNode) = - self.warnings.add(Warning(kind: kind, node: node)) - - -proc `$`*(self: Warning): string = &"Warning(kind={self.kind}, node={self.node})" - - -# Forward declaration -proc optimizeNode(self: Optimizer, node: ASTNode): ASTNode - - -proc optimizeConstant(self: Optimizer, node: ASTNode): ASTNode = - ## Performs some checks on constant AST nodes such as - ## integers. This method converts all of the different - ## integer forms (binary, octal and hexadecimal) to - ## decimal integers. Overflows are checked here too - if not self.foldConstants: - return node - case node.kind: - of intExpr: - var x: int - var y = IntExpr(node) - try: - discard parseInt(y.literal.lexeme, x) - except ValueError: - self.newWarning(valueOverflow, node) - result = node - of hexExpr: - var x: int - var y = HexExpr(node) - try: - discard parseHex(y.literal.lexeme, x) - except ValueError: - self.newWarning(valueOverflow, node) - return node - result = IntExpr(kind: intExpr, literal: Token(kind: Integer, lexeme: $x, line: y.literal.line, pos: (start: -1, stop: -1))) - of binExpr: - var x: int - var y = BinExpr(node) - try: - discard parseBin(y.literal.lexeme, x) - except ValueError: - self.newWarning(valueOverflow, node) - return node - result = IntExpr(kind: intExpr, literal: Token(kind: Integer, lexeme: $x, line: y.literal.line, pos: (start: -1, stop: -1))) - of octExpr: - var x: int - var y = OctExpr(node) - try: - discard parseOct(y.literal.lexeme, x) - except ValueError: - self.newWarning(valueOverflow, node) - return node - result = IntExpr(kind: intExpr, literal: Token(kind: Integer, lexeme: $x, line: y.literal.line, pos: (start: -1, stop: -1))) - of floatExpr: - var x: float - var y = FloatExpr(node) - try: - discard parseFloat(y.literal.lexeme, x) - except ValueError: - self.newWarning(valueOverflow, node) - return node - result = FloatExpr(kind: floatExpr, literal: Token(kind: Float, lexeme: $x, line: y.literal.line, pos: (start: -1, stop: -1))) - else: - result = node - - -proc optimizeUnary(self: Optimizer, node: UnaryExpr): ASTNode = - ## Attempts to optimize unary expressions - var a = self.optimizeNode(node.a) - if self.warnings.len() > 0 and self.warnings[^1].kind == valueOverflow and self.warnings[^1].node == a: - # We can't optimize further, the overflow will be caught in the compiler - return UnaryExpr(kind: unaryExpr, a: a, operator: node.operator) - case a.kind: - of intExpr: - var x: int - discard parseInt(IntExpr(a).literal.lexeme, x) - case node.operator.kind: - of Tilde: - x = not x - of Minus: - x = -x - else: - discard # Unreachable - result = IntExpr(kind: intExpr, literal: Token(kind: Integer, lexeme: $x, line: node.operator.line, pos: (start: -1, stop: -1))) - of floatExpr: - var x: float - discard parseFloat(FloatExpr(a).literal.lexeme, x) - case node.operator.kind: - of Minus: - x = -x - of Tilde: - self.newWarning(invalidOperation, node) - return node - else: - discard - result = FloatExpr(kind: floatExpr, literal: Token(kind: Float, lexeme: $x, line: node.operator.line, pos: (start: -1, stop: -1))) - else: - result = node - - -proc optimizeBinary(self: Optimizer, node: BinaryExpr): ASTNode = - ## Attempts to optimize binary expressions - var a, b: ASTNode - a = self.optimizeNode(node.a) - b = self.optimizeNode(node.b) - if self.warnings.len() > 0 and self.warnings[^1].kind == valueOverflow and (self.warnings[^1].node == a or self.warnings[^1].node == b): - # We can't optimize further: the overflow will be caught in the compiler. We don't return the same node - # because optimizeNode might've been able to optimize one of the two operands and we don't know which - return newBinaryExpr(a, node.operator, b) - if node.operator.kind == DoubleEqual: - if a.kind in {trueExpr, falseExpr, nilExpr, nanExpr, infExpr}: - self.newWarning(equalityWithSingleton, a) - elif b.kind in {trueExpr, falseExpr, nilExpr, nanExpr, infExpr}: - self.newWarning(equalityWithSingleton, b) - elif node.operator.kind == Is: - if a.kind in {strExpr, intExpr, tupleExpr, dictExpr, listExpr, setExpr}: - self.newWarning(isWithALiteral, a) - elif b.kind in {strExpr, intExpr, tupleExpr, dictExpr, listExpr, setExpr}: - self.newWarning(isWithALiteral, b) - if a.kind == intExpr and b.kind == intExpr: - # Optimizes integer operations - var x, y, z: int - discard parseInt(IntExpr(a).literal.lexeme, x) - discard parseInt(IntExpr(b).literal.lexeme, y) - try: - case node.operator.kind: - of Plus: - z = x + y - of Minus: - z = x - y - of Star: - z = x * y - of FloorDiv: - z = int(x / y) - of DoubleStar: - if y >= 0: - z = x ^ y - else: - # Nim's builtin pow operator can't handle - # negative exponents, so we use math's - # pow and convert from/to floats instead - z = pow(x.float, y.float).int - of Percentage: - z = x mod y - of Caret: - z = x xor y - of Ampersand: - z = x and y - of Pipe: - z = x or y - of Slash: - # Special case, yields a float - return newFloatExpr(Token(kind: Float, lexeme: $(x / y), line: IntExpr(a).literal.line, pos: (start: -1, stop: -1))) - else: - result = newBinaryExpr(a, node.operator, b) - except OverflowDefect: - self.newWarning(valueOverflow, node) - return newBinaryExpr(a, node.operator, b) - except RangeDefect: - # TODO: What warning do we raise here? - return newBinaryExpr(a, node.operator, b) - result = newIntExpr(Token(kind: Integer, lexeme: $z, line: IntExpr(a).literal.line, pos: (start: -1, stop: -1))) - elif a.kind == floatExpr or b.kind == floatExpr: - var x, y, z: float - if a.kind == intExpr: - var temp: int - discard parseInt(IntExpr(a).literal.lexeme, temp) == IntExpr(a).literal.lexeme.len() - x = float(temp) - self.newWarning(implicitConversion, a) - else: - discard parseFloat(FloatExpr(a).literal.lexeme, x) - if b.kind == intExpr: - var temp: int - discard parseInt(IntExpr(b).literal.lexeme, temp) == IntExpr(b).literal.lexeme.len() - y = float(temp) - self.newWarning(implicitConversion, b) - else: - discard parseFloat(FloatExpr(b).literal.lexeme, y) - # Optimizes float operations - try: - case node.operator.kind: - of Plus: - z = x + y - of Minus: - z = x - y - of Star: - z = x * y - of FloorDiv, Slash: - z = x / y - of DoubleStar: - z = pow(x, y) - of Percentage: - z = x mod y - else: - result = newBinaryExpr(a, node.operator, b) - except OverflowDefect: - self.newWarning(valueOverflow, node) - return newBinaryExpr(a, node.operator, b) - result = newFloatExpr(Token(kind: Float, lexeme: $z, line: LiteralExpr(a).literal.line, pos: (start: -1, stop: -1))) - elif a.kind == strExpr and b.kind == strExpr: - var a = StrExpr(a) - var b = StrExpr(b) - case node.operator.kind: - of Plus: - result = newStrExpr(Token(kind: String, lexeme: "'" & a.literal.lexeme[1..<(^1)] & b.literal.lexeme[1..<(^1)] & "'", pos: (start: -1, stop: -1))) - else: - result = node - elif a.kind == strExpr and self.optimizeNode(b).kind == intExpr and not (self.warnings.len() > 0 and self.warnings[^1].kind == valueOverflow and self.warnings[^1].node == b): - var a = StrExpr(a) - var b = IntExpr(b) - var bb: int - discard parseInt(b.literal.lexeme, bb) - case node.operator.kind: - of Star: - result = newStrExpr(Token(kind: String, lexeme: "'" & a.literal.lexeme[1..<(^1)].repeat(bb) & "'")) - else: - result = node - elif b.kind == strExpr and self.optimizeNode(a).kind == intExpr and not (self.warnings.len() > 0 and self.warnings[^1].kind == valueOverflow and self.warnings[^1].node == a): - var b = StrExpr(b) - var a = IntExpr(a) - var aa: int - discard parseInt(a.literal.lexeme, aa) - case node.operator.kind: - of Star: - result = newStrExpr(Token(kind: String, lexeme: "'" & b.literal.lexeme[1..<(^1)].repeat(aa) & "'")) - else: - result = node - else: - # There's no constant folding we can do! - result = node - - -proc optimizeNode(self: Optimizer, node: ASTNode): ASTNode = - ## Analyzes an AST node and attempts to perform - ## optimizations on it. If no optimizations can be - ## applied or self.foldConstants is set to false, - ## then the same node is returned - if not self.foldConstants: - return node - case node.kind: - of exprStmt: - result = newExprStmt(self.optimizeNode(ExprStmt(node).expression), ExprStmt(node).token) - of intExpr, hexExpr, octExpr, binExpr, floatExpr, strExpr: - result = self.optimizeConstant(node) - of unaryExpr: - result = self.optimizeUnary(UnaryExpr(node)) - of binaryExpr: - result = self.optimizeBinary(BinaryExpr(node)) - of groupingExpr: - # Recursively unnests groups - result = self.optimizeNode(GroupingExpr(node).expression) - of callExpr: - var node = CallExpr(node) - for i, positional in node.arguments.positionals: - node.arguments.positionals[i] = self.optimizeNode(positional) - for i, (key, value) in node.arguments.keyword: - node.arguments.keyword[i].value = self.optimizeNode(value) - result = node - of sliceExpr: - var node = SliceExpr(node) - for i, e in node.ends: - node.ends[i] = self.optimizeNode(e) - node.expression = self.optimizeNode(node.expression) - result = node - of tryStmt: - var node = TryStmt(node) - node.body = self.optimizeNode(node.body) - if node.finallyClause != nil: - node.finallyClause = self.optimizeNode(node.finallyClause) - if node.elseClause != nil: - node.elseClause = self.optimizeNode(node.elseClause) - for i, handler in node.handlers: - node.handlers[i].body = self.optimizeNode(node.handlers[i].body) - result = node - of funDecl: - var decl = FunDecl(node) - for i, node in decl.defaults: - decl.defaults[i] = self.optimizeNode(node) - decl.body = self.optimizeNode(decl.body) - result = decl - of blockStmt: - var node = BlockStmt(node) - for i, n in node.code: - node.code[i] = self.optimizeNode(n) - result = node - of varDecl: - var decl = VarDecl(node) - decl.value = self.optimizeNode(decl.value) - result = decl - of assignExpr: - var asgn = AssignExpr(node) - asgn.value = self.optimizeNode(asgn.value) - result = asgn - of listExpr: - var l = ListExpr(node) - for i, e in l.members: - l.members[i] = self.optimizeNode(e) - result = node - of setExpr: - var s = SetExpr(node) - for i, e in s.members: - s.members[i] = self.optimizeNode(e) - result = node - of tupleExpr: - var t = TupleExpr(node) - for i, e in t.members: - t.members[i] = self.optimizeNode(e) - result = node - of dictExpr: - var d = DictExpr(node) - for i, e in d.keys: - d.keys[i] = self.optimizeNode(e) - for i, e in d.values: - d.values[i] = self.optimizeNode(e) - result = node - else: - result = node - - -proc optimize*(self: Optimizer, tree: seq[ASTNode]): tuple[tree: seq[ASTNode], warnings: seq[Warning]] = - ## Runs the optimizer on the given source - ## tree and returns a new optimized tree - ## as well as a list of warnings that may - ## be of interest. The input tree may be - ## identical to the output tree if no optimization - ## could be performed. Constant folding can be - ## turned off by setting foldConstants to false - ## when initializing the optimizer object. This - ## optimization step also takes care of detecting - ## closed-over variables so that the compiler can - ## emit appropriate instructions for them later on - var newTree: seq[ASTNode] = @[] - for node in tree: - newTree.add(self.optimizeNode(node)) - result = (tree: newTree, warnings: self.warnings) diff --git a/src/frontend/parser.nim b/src/frontend/parser.nim index 5691e81..9971746 100644 --- a/src/frontend/parser.nim +++ b/src/frontend/parser.nim @@ -898,6 +898,11 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL returnType=nil) elif not isOperator: self.error("funDecl: invalid state") + # Beware: lots of code duplication ahead. I agree, + # it's disgusting, but each case of argument parsing + # is specialized for a given context and is hard to + # generalize elegantly into a single function that + # makes sense if self.match(Colon): # Function has explicit return type if self.match([Function, Coroutine, Generator]): @@ -1040,7 +1045,7 @@ proc funDecl(self: Parser, isAsync: bool = false, isGenerator: bool = false, isL proc expression(self: Parser): Expression = ## Parses expressions - result = self.assignment() + result = self.assignment() # Highest-level expression proc expressionStatement(self: Parser): Statement = diff --git a/src/test.nim b/src/test.nim index 471dd8e..b442110 100644 --- a/src/test.nim +++ b/src/test.nim @@ -17,8 +17,6 @@ import frontend/lexer as l import frontend/parser as p import frontend/compiler as c import backend/vm as v -# TODO: Broken -# import frontend/optimizer as o import util/serializer as s import util/debugger @@ -29,10 +27,9 @@ proc getLineEditor: LineEditor # Handy dandy compile-time constants const debugLexer = false -const debugParser = true -const debugCompiler = false -const debugOptimizer = false -const debugSerializer = false +const debugParser = false +const debugCompiler = true +const debugSerializer = true when isMainModule: @@ -42,12 +39,10 @@ when isMainModule: tokens: seq[Token] = @[] tree: seq[ASTNode] = @[] compiled: Chunk - # optimized: tuple[tree: seq[ASTNode], warnings: seq[Warning]] serialized: Serialized serializedRaw: seq[byte] tokenizer = newLexer() parser = newParser() - # optimizer = newOptimizer() compiler = newCompiler() serializer = newSerializer() vm = newPeonVM() @@ -83,22 +78,6 @@ when isMainModule: for node in tree: echo "\t", node echo "" - # The optimizer needs work to function properly - # with the compiler - # optimized = optimizer.optimize(tree) - when debugOptimizer: - echo &"Optimization step (constant folding enabled: {optimizer.foldConstants}):" - for node in optimized.tree: - echo "\t", node - echo "" - stdout.write(&"Produced warnings: ") - if optimized.warnings.len() > 0: - echo "" - for warning in optimized.warnings: - echo "\t", warning - else: - stdout.write("No warnings produced\n") - echo "" compiled = compiler.compile(tree, "") when debugCompiler: echo "Compilation step:"