Minor improvements to modularization (moved even more procedures to generalized methods)

This commit is contained in:
Mattia Giambirtone 2022-12-16 14:32:20 +01:00
parent db021cb821
commit ffe57c134a
5 changed files with 234 additions and 19 deletions

View File

@ -233,6 +233,9 @@ method findInModule*(self: Compiler, name: string, module: Name): seq[Name]
method findByType*(self: Compiler, name: string, kind: Type): seq[Name]
method compare*(self: Compiler, a, b: Type): bool
method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name
method prepareFunction*(self: Compiler, name: Name) {.base.} = discard
method dispatchPragmas(self: Compiler, name: Name) {.base.} = discard
method dispatchDelayedPragmas(self: Compiler, name: Name) {.base.} = discard
## End of forward declarations
## Utility functions
@ -815,3 +818,207 @@ method match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, all
for (a, b) in zip(result.valueType.args, kind.args):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
proc beginScope*(self: Compiler) =
## Begins a new local scope by incrementing the current
## scope's depth
inc(self.depth)
proc unpackGenerics*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type constraint in a generic type
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
if list[^1].kind.kind == Auto:
self.error("automatic types cannot be used within generics", condition)
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackGenerics(condition.a, list)
self.unpackGenerics(condition.b, list)
else:
self.error("invalid type constraint in generic declaration", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackGenerics(condition.a, list, accept=false)
else:
self.error("invalid type constraint in generic declaration", condition)
else:
self.error("invalid type constraint in generic declaration", condition)
proc unpackUnion*(self: Compiler, condition: Expression, list: var seq[tuple[match: bool, kind: Type]], accept: bool = true) =
## Recursively unpacks a type union
case condition.kind:
of identExpr:
list.add((accept, self.inferOrError(condition)))
of binaryExpr:
let condition = BinaryExpr(condition)
case condition.operator.lexeme:
of "|":
self.unpackUnion(condition.a, list)
self.unpackUnion(condition.b, list)
else:
self.error("invalid type constraint in type union", condition)
of unaryExpr:
let condition = UnaryExpr(condition)
case condition.operator.lexeme:
of "~":
self.unpackUnion(condition.a, list, accept=false)
else:
self.error("invalid type constraint in type union", condition)
else:
self.error("invalid type constraint in type union", condition)
proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
## Statically declares a name into the current scope.
## "Declaring" a name only means updating our internal
## list of identifiers so that further calls to resolve()
## correctly return them. There is no code to actually
## declare a variable at runtime: the value is already
## on the stack
var declaredName: string = ""
var n: Name
if self.names.high() > 16777215:
# If someone ever hits this limit in real-world scenarios, I swear I'll
# slap myself 100 times with a sign saying "I'm dumb". Mark my words
self.error("cannot declare more than 16777215 names at a time")
case node.kind:
of NodeKind.varDecl:
var node = VarDecl(node)
declaredName = node.name.token.lexeme
# Creates a new Name entry so that self.identifier emits the proper stack offset
self.names.add(Name(depth: self.depth,
ident: node.name,
isPrivate: node.isPrivate,
owner: self.currentModule,
file: self.file,
isConst: node.isConst,
valueType: nil, # Done later
isLet: node.isLet,
line: node.token.line,
belongsTo: self.currentFunction,
kind: NameKind.Var,
node: node,
isReal: true
))
n = self.names[^1]
of NodeKind.funDecl:
var node = FunDecl(node)
declaredName = node.name.token.lexeme
var fn = Name(depth: self.depth,
isPrivate: node.isPrivate,
isConst: false,
owner: self.currentModule,
file: self.file,
valueType: Type(kind: Function,
returnType: nil, # We check it later
args: @[],
fun: node,
forwarded: node.body.isNil(),
isAuto: false),
ident: node.name,
node: node,
isLet: false,
line: node.token.line,
kind: NameKind.Function,
belongsTo: self.currentFunction,
isReal: true)
if node.isTemplate:
fn.valueType.compiled = true
if node.generics.len() > 0:
fn.isGeneric = true
var typ: Type
for argument in node.arguments:
typ = self.infer(argument.valueType)
if not typ.isNil() and typ.kind == Auto:
fn.valueType.isAuto = true
if fn.isGeneric:
self.error("automatic types cannot be used within generics", argument.valueType)
break
typ = self.infer(node.returnType)
if not typ.isNil() and typ.kind == Auto:
fn.valueType.isAuto = true
if fn.isGeneric:
self.error("automatic types cannot be used within generics", node.returnType)
self.names.add(fn)
self.prepareFunction(fn)
n = fn
of NodeKind.importStmt:
var node = ImportStmt(node)
# We change the name of the module internally so that
# if you import /path/to/mod, then doing mod.f() will
# still work without any extra work on our end. Note how
# we don't change the metadata about the identifier's
# position so that error messages still highlight the
# full path
let path = node.moduleName.token.lexeme
node.moduleName.token.lexeme = node.moduleName.token.lexeme.extractFilename()
self.names.add(Name(depth: self.depth,
owner: self.currentModule,
file: "", # The file of the module isn't known until it's compiled!
path: path,
ident: node.moduleName,
line: node.moduleName.token.line,
kind: NameKind.Module,
isPrivate: false,
isReal: true
))
n = self.names[^1]
declaredName = self.names[^1].ident.token.lexeme
of NodeKind.typeDecl:
var node = TypeDecl(node)
self.names.add(Name(kind: NameKind.CustomType,
depth: self.depth,
owner: self.currentModule,
node: node,
ident: node.name,
line: node.token.line,
isPrivate: node.isPrivate,
isReal: true,
belongsTo: self.currentFunction
)
)
n = self.names[^1]
declaredName = node.name.token.lexeme
if node.value.isNil():
discard # TODO: Fields
else:
case node.value.kind:
of identExpr:
n.valueType = self.inferOrError(node.value)
of binaryExpr:
# Type union
n.valueType = Type(kind: Union, types: @[])
self.unpackUnion(node.value, n.valueType.types)
else:
discard
else:
discard # TODO: enums
if not n.isNil():
self.dispatchPragmas(n)
for name in self.findByName(declaredName):
if name == n:
continue
# We don't check for name clashes with functions because self.match() does that
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum] and name.depth == n.depth and name.owner == n.owner:
self.error(&"re-declaration of {declaredName} is not allowed (previously declared in {name.owner.ident.token.lexeme}:{name.ident.token.line}:{name.ident.token.relPos.start})")
for name in self.names:
if name == n:
break
if name.ident.token.lexeme != declaredName:
continue
if name.owner != n.owner and (name.isPrivate or n.owner notin name.exportedTo):
continue
if name.kind in [NameKind.Var, NameKind.Module, NameKind.CustomType, NameKind.Enum]:
if name.depth < n.depth:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
elif name.owner != n.owner:
self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n)
return n

View File

@ -114,12 +114,12 @@ proc patchReturnAddress(self: BytecodeCompiler, pos: int)
proc handleMagicPragma(self: BytecodeCompiler, pragma: Pragma, name: BytecodeName)
proc handlePurePragma(self: BytecodeCompiler, pragma: Pragma, name: BytecodeName)
proc handleErrorPragma(self: BytecodeCompiler, pragma: Pragma, name: BytecodeName)
proc dispatchPragmas(self: BytecodeCompiler, name: BytecodeName)
proc dispatchDelayedPragmas(self: BytecodeCompiler, name: BytecodeName)
method dispatchPragmas(self: BytecodeCompiler, name: BytecodeName)
method dispatchDelayedPragmas(self: BytecodeCompiler, name: BytecodeName)
proc funDecl(self: BytecodeCompiler, node: FunDecl, name: BytecodeName)
proc compileModule(self: BytecodeCompiler, module: BytecodeName)
proc generateCall(self: BytecodeCompiler, fn: BytecodeName, args: seq[Expression], line: int)
proc prepareFunction(self: BytecodeCompiler, fn: BytecodeName)
method prepareFunction(self: BytecodeCompiler, fn: BytecodeName)
# End of forward declarations
@ -544,12 +544,6 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio
self.error(&"unknown built-in: '{fn.builtinOp}'", fn.fun)
proc beginScope(self: BytecodeCompiler) =
## Begins a new local scope by incrementing the current
## scope's depth
inc(self.depth)
proc patchForwardDeclarations(self: BytecodeCompiler) =
## Patches forward declarations and looks
## for their implementations so that calls
@ -914,7 +908,7 @@ proc handlePurePragma(self: BytecodeCompiler, pragma: Pragma, name: BytecodeName
self.error("'pure' pragma is not valid in this context")
proc dispatchPragmas(self: BytecodeCompiler, name: BytecodeName) =
method dispatchPragmas(self: BytecodeCompiler, name: BytecodeName) =
## Dispatches pragmas bound to objects
if name.node.isNil():
return
@ -936,7 +930,7 @@ proc dispatchPragmas(self: BytecodeCompiler, name: BytecodeName) =
f.handler(self, pragma, name)
proc dispatchDelayedPragmas(self: BytecodeCompiler, name: BytecodeName) =
method dispatchDelayedPragmas(self: BytecodeCompiler, name: BytecodeName) =
## Dispatches pragmas bound to objects once they
## are called. Only applies to functions
if name.node.isNil():
@ -989,7 +983,7 @@ proc generateCall(self: BytecodeCompiler, fn: Type, args: seq[Expression], line:
self.patchReturnAddress(pos)
proc prepareFunction(self: BytecodeCompiler, fn: BytecodeName) =
method prepareFunction(self: BytecodeCompiler, fn: BytecodeName) =
## "Prepares" a function declaration by declaring
## its arguments and typechecking it

View File

@ -67,4 +67,5 @@ proc newNativeCCompiler*(replMode: bool = false): NativeCCompiler =
method literal*(self: Compiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
## Compiles literal expressions

View File

@ -14,8 +14,21 @@
## Reusable objects to simplify code generation using templates
type
CodeGenerator* = ref object of RootObj
type
GeneratorKind* = enum
## A code generator enumeration
Literal,
CodeGenerator* = object
## A generic code generator
code: string
vars: seq[tuple[name, value: string]]
case kind*: GeneratorKind
of Literal:
lit: string
proc generate*(self: CodeGenerator): string =
## Generates the source code for the given
## code generator object and returns it
case self.kind:
of Literal:
return self.lit

View File

@ -89,12 +89,12 @@ operator `/`*[T: UnsignedInteger](a, b: T): T {
operator `/`*(a, b: float64): float64 {
#pragma[magic: "DivFloat64", pure]
#pragma[magic: "DivideFloat64", pure]
}
operator `/`*(a, b: float32): float32 {
#pragma[magic: "DivFloat32", pure]
#pragma[magic: "DivideFloat32", pure]
}