also derete this #3

Closed
N00nehere wants to merge 49 commits from (deleted):n00nehere-patch-2 into master
4 changed files with 13 additions and 411 deletions
Showing only changes of commit 299643e8be - Show all commits

View File

@ -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

View File

@ -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)

View File

@ -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 =

View File

@ -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, "<stdin>")
when debugCompiler:
echo "Compilation step:"