diff --git a/src/config.nim b/src/config.nim index 06217e9..35936ae 100644 --- a/src/config.nim +++ b/src/config.nim @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import strformat +import std/strformat +import std/os + # These variables can be tweaked to debug and test various components of the toolchain var debugLexer* = false # Print the tokenizer's output @@ -30,7 +32,7 @@ const HeapGrowFactor* = 2 # The growth factor used by the G const FirstGC* = 1024 * 1024; # How many bytes to allocate before running the first GC const enableVMChecks* {.booldefine.} = true; # Enables all types of compiler (nim-wise) checks in the VM # List of paths where peon looks for modules, in order (empty path means current directory, which always takes precedence) -const moduleLookupPaths*: seq[string] = @["", "src/peon/stdlib", "/home/nocturn9x/.local/peon/stdlib"] +const moduleLookupPaths*: seq[string] = @["", "src/peon/stdlib", absolutePath(joinPath(".local", "peon", "stdlib"), getenv("HOME"))] when HeapGrowFactor <= 1: {.fatal: "Heap growth factor must be > 1".} const PeonVersion* = (major: 0, minor: 1, patch: 0) diff --git a/src/frontend/compiler/compiler.nim b/src/frontend/compiler/compiler.nim index cca7a67..136d2e1 100644 --- a/src/frontend/compiler/compiler.nim +++ b/src/frontend/compiler/compiler.nim @@ -100,6 +100,9 @@ type case kind*: NameKind of NameKind.Module: path*: string + # Full absolute path of the module, + # including the extension + absPath*: string else: discard # The name's identifier @@ -134,7 +137,7 @@ type node*: Declaration # Who is this name exported to? (Only makes sense if isPrivate # equals false) - exportedTo*: HashSet[string] + exportedTo*: seq[Name] # Has the compiler generated this name internally or # does it come from user code? isReal*: bool @@ -346,7 +349,7 @@ proc resolve*(self: Compiler, name: string): Name = ## Returns nil when the name can't be found for obj in reversed(self.names): if obj.ident.token.lexeme == name: - if obj.owner.path != self.currentModule.path: + if obj.owner.absPath != self.currentModule.absPath: # We don't own this name, but we # may still have access to it if obj.isPrivate: @@ -354,7 +357,7 @@ proc resolve*(self: Compiler, name: string): Name = # module, so we definitely can't # use it continue - if self.currentModule.path in obj.exportedTo: + if self.currentModule in obj.exportedTo: # The name is public in its owner # module and said module has explicitly # exported it to us: we can use it @@ -712,8 +715,8 @@ method findByName*(self: Compiler, name: string): seq[Name] = ## with the given name. Returns all objects that apply. for obj in reversed(self.names): if obj.ident.token.lexeme == name: - if obj.owner.path != self.currentModule.path: - if obj.isPrivate or self.currentModule.path notin obj.exportedTo: + if obj.owner.absPath != self.currentModule.absPath: + if obj.isPrivate or self.currentModule notin obj.exportedTo: continue result.add(obj) @@ -729,11 +732,11 @@ method findInModule*(self: Compiler, name: string, module: Name): seq[Name] = for obj in reversed(self.names): if obj.owner.isNil(): continue - if not obj.isPrivate and obj.owner.path == module.path: + if not obj.isPrivate and obj.owner.absPath == module.absPath: result.add(obj) else: for obj in self.findInModule("", module): - if obj.ident.token.lexeme == name and self.currentModule.path in obj.exportedTo: + if obj.ident.token.lexeme == name and self.currentModule in obj.exportedTo: result.add(obj) @@ -1036,7 +1039,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} = break if name.ident.token.lexeme != declaredName: continue - if name.owner != n.owner and (name.isPrivate or n.owner.path notin name.exportedTo): + 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: diff --git a/src/frontend/compiler/newcompiler.nim b/src/frontend/compiler/newcompiler.nim deleted file mode 100644 index 430f635..0000000 --- a/src/frontend/compiler/newcompiler.nim +++ /dev/null @@ -1,1056 +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 std/os -import std/sets -import std/tables -import std/hashes -import std/strutils -import std/terminal -import std/sequtils -import std/algorithm -import std/strformat - -import errors -import config -import frontend/parsing/token -import frontend/parsing/ast -import frontend/parsing/lexer as l -import frontend/parsing/parser as p - - -type - # Just a bunch of convenience type aliases - TypedArgument* = tuple[name: string, kind: Type, default: TypedNode] - TypeConstraint* = tuple[match: bool, kind: Type] - - PragmaKind* = enum - ## A pragma type enumeration - - # "Immediate" pragmas are processed right - # when they are encountered. This is useful - # for some types of pragmas, such as those - # that flick compile-time switches on or off - # or that mark objects with some compile-time - # property. "Delayed" pragmas, on the other hand, - # are processed when their associated object is - # used in some way. For example, the "error" - # pragma, when associated to a function, causes - # the compiler to raise a static error when - # attempting to call said function, rather than - # doing so at declaration time. This allows the - # detection of errors such as trying to negate - # unsigned integers without explicitly hardcoding - # the check into the compiler (all that's needed - # is a definition of the `-` operator that will - # raise a static error once called) - Immediate, Delayed - - Pragma* = object - ## A pragma object. Pragmas are - ## (usually) hooks into compiler - ## functions or serve as markers - ## for objects (for example, to - ## signal that a function has no - ## side effects or that a type is - ## nullable) - kind*: PragmaKind - name*: string # All pragmas have names - arguments*: seq[Type] # The arguments to the pragma. Must be values computable at compile time - - TypeKind* = enum - ## An enumeration of compile-time - ## types - - # Intrinsic (aka "built-in") types - - # Signed and unsigned integer types - Int8, UInt8, Int16, UInt16, - Int32, UInt32, Int64, UInt64, - - # Floating point types - Float32, Float64, - - Char, # A single ASCII character - Byte, # Basically an alias for char - String, # A string. No encoding is specified - Function, # A function - TypeDecl, # A type declaration - Nil, # The nil type (aka null) - Nan, # The value of NaN (Not a Number) - Bool, # Booleans (true and false) - Inf, # Negative and positive infinity - # Note: nil, nan, true, false and inf are all singletons - Typevar, # A type variable is the type of a type. For example, the type of `int` is typevar - Generic, # A parametrically polymorphic generic type - Reference, # A managed (aka GC'ed) reference - Pointer, # An unmanaged (aka malloc()-ed) reference - Any, # The "any" type is a placeholder for a single type (similar to Python's builtins.object) - All, # The all type means "any type or no type at all". It is not exposed outside of the compiler - Union, # An untagged type union (acts like an exclusive "logical or" constraint) - Auto, # An automatic type. The compiler infers the true type of the object from its value when necessary - Enum, # An enumeration type - - Type* = ref object - ## A compile-time type - case kind: TypeKind: - of Generic: - # A generic type - constraints*: seq[TypeConstraint] # The type's generic constraints. For example, - # fn foo[T*: int & ~uint](...) {...} would map to [(true, int), (false, uint)] - name*: IdentExpr # The generic's name (in our example above, this would be "T") - asUnion*: bool # A generic constraint is treated like a "logical and", which means all - # of its constraints must be satisfied. This allows for parametric polymorphism to work, - # but it woudln't allow to make use of the type with only one of the types of the constraint, - # which is pretty useless. When this value is set to true, which it isn't by default, the - # constraints turn into an exclusive "logical or" instead, meaning that any type in the constraints - # is a valid instance of the type itself. This allows the compiler to typecheck the type for all - # possible types in the constraint and then let the user instantiate said type with any of the types - # in said constraint. The field's name means "treat this generic constraint like a type union" - of Union: - # A type union - types*: seq[TypeConstraint] - of Reference: - # A managed reference - nullable*: bool # Is null a valid value for this type? (false by default) - value*: TypedNode # The type the reference points to - of Pointer: - # An unmanaged reference. Much - # like a raw pointer in C - data*: TypedNode # The type we point to - of TypeDecl: - # A user-defined type - fields*: seq[TypedArgument] # List of fields in the object. May be empty - parent*: Type # The parent of this object if inheritance is used. May be nil - implements*: seq[Type] # The interfaces this object implements. May be empty - of Function: - # A function-like object. Wraps regular - # functions, lambdas, coroutines and generators - isLambda*: bool # Is this a lambda (aka anonymous) function? - isCoroutine*: bool # Is this a coroutine? - isGenerator*: bool # Is this a generator? - isAuto*: bool # Is this an automatic function? - arguments*: seq[TypedArgument] # The function's arguments - forwarded*: bool # Is this a forward declaration? - returnType*: Type # The function's return type - else: - discard - # Can this type be mutated? - mutable: bool - - TypedNode* = ref object - ## A typed AST node - node*: ASTNode # The original (typeless) AST node - value*: Type # The node's type - - NameKind* = enum - ## A name enumeration type - DeclType, # Any type declaration - Module - - Name* = ref object - ## A name object. Name objects associate - ## peon objects to identifiers - case kind*: NameKind - of Module: - path*: string # The module's path - else: - discard - ident*: IdentExpr # The name's identifier - file*: string # The file where this name is declared in - belongsTo*: Name # The function owning this name, if any - obj*: TypedNode # The name's associated object - owner*: Name # The module owning this name - depth*: int # The name's scope depth - isPrivate*: bool # Is this name private? - isConst*: bool # Is this name a constant? - isLet*: bool # Can this name's value be mutated? - isGeneric*: bool # Is this a generic type? - line*: int # The line where this name is declared - resolved*: bool # Has this name ever been used? - node*: Declaration # The declaration associated with this name - exports*: HashSet[Name] # The modules to which this name is visible to - isBuiltin*: bool # Is this name a built-in? - isReal*: bool # Is this an actual name in user code? (The compiler - # generates some names for its internal use and they may even duplicate existing - # ones, so that is why we need this attribute) - - WarningKind* {.pure.} = enum - ## A warning enumeration type - UnreachableCode, UnusedName, ShadowOuterScope, - MutateOuterScope - - CompileMode* {.pure.} = enum - ## A compilation mode enumeration - Debug, Release - - CompileError* = ref object of PeonException - node*: ASTNode - function*: Declaration - compiler*: Compiler - - Compiler* = ref object - ## The peon compiler - ast: seq[Declaration] # The (typeless) AST of the current module - current: int # Index into self.ast of the current node we're compiling - file*: string # The current file being compiled (used only for error reporting) - depth*: int # The current scope depth. If > 0, we're in a local scope, otherwise it's global - replMode*: bool # Are we in REPL mode? - names*: seq[Name] # List of all currently declared names - lines*: seq[tuple[start, stop: int]] # Stores line data for error reporting - source*: string # The source of the current module, used for error reporting - # We store these objects to compile modules - lexer*: Lexer - parser*: Parser - isMainModule*: bool # Are we compiling the main module? - disabledWarnings*: seq[WarningKind] # List of disabled warnings - showMismatches*: bool # Whether to show detailed info about type mismatches when we dispatch - mode*: CompileMode # Are we compiling in debug mode or release mode? - currentFunction*: Name # The current function being compiled - currentModule*: Name # The current module being compiled - parentModule*: Name # The module importing us, if any - modules*: HashSet[Name] # Currently imported modules - - -# Makes our name objects hashable -func hash(self: Name): Hash {.inline.} = self.ident.token.lexeme.hash() - -proc `$`*(self: Name): string = $(self[]) -proc `$`(self: Type): string = $(self[]) -proc `$`(self: TypedNode): string = $(self[]) - - -# Public getters for nicer error formatting -func getCurrentNode*(self: Compiler): ASTNode {.inline.} = (if self.current >= self.ast.len(): self.ast[^1] else: self.ast[self.current - 1]) -func getCurrentFunction*(self: Compiler): Declaration {.inline.} = (if self.currentFunction.isNil(): nil else: self.currentFunction.node) -func getSource*(self: Compiler): string {.inline.} = self.source - -# Utility functions - -proc peek*(self: Compiler, distance: int = 0): ASTNode = - ## Peeks at the AST node at the given distance. - ## If the distance is out of bounds, the last - ## AST node in the tree is returned. A negative - ## distance may be used to retrieve previously - ## consumed AST nodes - if self.ast.high() == -1 or self.current + distance > self.ast.high() or self.current + distance < 0: - result = self.ast[^1] - else: - result = self.ast[self.current + distance] - - -proc done*(self: Compiler): bool {.inline.} = - ## Returns true if the compiler is done - ## compiling, false otherwise - result = self.current > self.ast.high() - - -proc error*(self: Compiler, message: string, node: ASTNode = nil) {.inline.} = - ## Raises a CompileError exception - let node = if node.isNil(): self.getCurrentNode() else: node - raise CompileError(msg: message, node: node, line: node.token.line, file: node.file, compiler: self) - - -proc warning*(self: Compiler, kind: WarningKind, message: string, name: Name = nil, node: ASTNode = nil) = - ## Raises a warning. Note that warnings are always disabled in REPL mode - if self.replMode or kind in self.disabledWarnings: - return - var node: ASTNode = node - var fn: Declaration - if name.isNil(): - if node.isNil(): - node = self.getCurrentNode() - fn = self.getCurrentFunction() - else: - node = name.node - if node.isNil(): - node = self.getCurrentNode() - if not name.belongsTo.isNil(): - fn = name.belongsTo.node - else: - fn = self.getCurrentFunction() - var file = self.file - if not name.isNil(): - file = name.owner.file - var pos = node.getRelativeBoundaries() - if file notin ["", ""]: - file = relativePath(file, getCurrentDir()) - stderr.styledWrite(fgYellow, styleBright, "Warning in ", fgRed, &"{file}:{node.token.line}:{pos.start}") - if not fn.isNil() and fn.kind == funDecl: - stderr.styledWrite(fgYellow, styleBright, " in function ", fgRed, FunDecl(fn).name.token.lexeme) - stderr.styledWriteLine(styleBright, fgDefault, ": ", message) - try: - # We try to be as specific as possible with the warning message, pointing to the - # line it belongs to, but since warnings are not always raised from the source - # file they're generated in, we take into account the fact that retrieving the - # exact warning location may fail and bail out silently if it does - let line = readFile(file).splitLines()[node.token.line - 1].strip(chars={'\n'}) - stderr.styledWrite(fgYellow, styleBright, "Source line: ", resetStyle, fgDefault, line[0.. a.len(): - long = b - short = a - var i = 0 - for cond1 in short: - for cond2 in long: - if not self.compare(cond1.kind, cond2.kind) or cond1.match != cond2.match: - continue - inc(i) - return i >= short.len() - - -proc toIntrinsic*(name: string): Type = - ## Converts a string to an intrinsic - ## type if it is valid and returns nil - ## otherwise - if name == "any": - return Type(kind: Any) - elif name == "all": - return Type(kind: All) - elif name == "auto": - return Type(kind: Auto) - elif name in ["int", "int64", "i64"]: - return Type(kind: Int64) - elif name in ["uint64", "u64", "uint"]: - return Type(kind: UInt64) - elif name in ["int32", "i32"]: - return Type(kind: Int32) - elif name in ["uint32", "u32"]: - return Type(kind: UInt32) - elif name in ["int16", "i16", "short"]: - return Type(kind: Int16) - elif name in ["uint16", "u16"]: - return Type(kind: UInt16) - elif name in ["int8", "i8"]: - return Type(kind: Int8) - elif name in ["uint8", "u8"]: - return Type(kind: UInt8) - elif name in ["f64", "float", "float64"]: - return Type(kind: Float64) - elif name in ["f32", "float32"]: - return Type(kind: Float32) - elif name in ["byte", "b"]: - return Type(kind: Byte) - elif name in ["char", "c"]: - return Type(kind: Char) - elif name == "nan": - return Type(kind: TypeKind.Nan) - elif name == "nil": - return Type(kind: Nil) - elif name == "inf": - return Type(kind: TypeKind.Inf) - elif name == "bool": - return Type(kind: Bool) - elif name == "typevar": - return Type(kind: Typevar) - elif name == "string": - return Type(kind: String) - - -proc infer*(self: Compiler, node: LiteralExpr): TypedNode = - ## Infers the type of a given literal expression - if node.isNil(): - return nil - case node.kind: - of intExpr, binExpr, octExpr, hexExpr: - let size = node.token.lexeme.split("'") - if size.len() == 1: - return TypedNode(node: node, value: Type(kind: Int64)) - let typ = size[1].toIntrinsic() - if not self.compare(typ, nil): - return TypedNode(node: node, value: typ) - else: - self.error(&"invalid type specifier '{size[1]}' for int", node) - of floatExpr: - let size = node.token.lexeme.split("'") - if size.len() == 1: - return TypedNode(node: node, value: Type(kind: Float64)) - let typ = size[1].toIntrinsic() - if not typ.isNil(): - return TypedNode(node: node, value: typ) - else: - self.error(&"invalid type specifier '{size[1]}' for float", node) - of trueExpr: - return TypedNode(node: node, value: Type(kind: Bool)) - of falseExpr: - return TypedNode(node: node, value: Type(kind: Bool)) - of strExpr: - return TypedNode(node: node, value: Type(kind: String)) - else: - discard # Unreachable - - -proc infer*(self: Compiler, node: Expression): TypedNode = - ## Infers the type of a given expression and - ## returns it - if node.isNil(): - return nil - case node.kind: - of NodeKind.identExpr: - result = self.identifier(IdentExpr(node), compile=false, strict=false) - of NodeKind.unaryExpr: - result = self.unary(UnaryExpr(node), compile=false) - of NodeKind.binaryExpr: - result = self.binary(BinaryExpr(node), compile=false) - of {NodeKind.intExpr, NodeKind.hexExpr, NodeKind.binExpr, NodeKind.octExpr, - NodeKind.strExpr, NodeKind.falseExpr, NodeKind.trueExpr, NodeKind.floatExpr - }: - result = self.infer(LiteralExpr(node)) - of NodeKind.callExpr: - result = self.call(CallExpr(node), compile=false) - of NodeKind.refExpr: - result = TypedNode(node: node, value: Type(kind: Reference, value: self.infer(Ref(node).value))) - of NodeKind.ptrExpr: - result = TypedNode(node: node, value: Type(kind: Pointer, data: self.infer(Ptr(node).value))) - of NodeKind.groupingExpr: - result = self.infer(GroupingExpr(node).expression) - of NodeKind.getItemExpr: - result = self.getItemExpr(GetItemExpr(node), compile=false) - of NodeKind.lambdaExpr: - result = self.lambdaExpr(LambdaExpr(node), compile=false) - else: - discard # TODO - - -proc inferOrError*(self: Compiler, node: Expression): TypedNode = - ## Attempts to infer the type of - ## the given expression and raises an - ## error if it fails - result = self.infer(node) - if result.isNil(): - self.error("expression has no type", node) - - -proc stringify*(self: Compiler, typ: Type): string = - ## Returns the string representation of a - ## type object - if typ.isNil(): - return "nil" - case typ.kind: - of Int8, UInt8, Int16, UInt16, Int32, - UInt32, Int64, UInt64, Float32, Float64, - Char, Byte, String, Nil, TypeKind.Nan, Bool, - TypeKind.Inf, Auto: - result &= ($typ.kind).toLowerAscii() - of Pointer: - result &= &"ptr {self.stringify(typ)}" - of Reference: - result &= &"ref {self.stringify(typ)}" - of Any: - return "any" - of Union: - for i, condition in typ.types: - if i > 0: - result &= " | " - if not condition.match: - result &= "~" - result &= self.stringify(condition.kind) - of Generic: - for i, condition in typ.constraints: - if i > 0: - result &= " | " - if not condition.match: - result &= "~" - result &= self.stringify(condition.kind) - else: - discard - -proc stringify*(self: Compiler, typ: TypedNode): string = - ## Returns the string representation of a - ## type object - if typ.isNil(): - return "nil" - case typ.value.kind: - of Int8, UInt8, Int16, UInt16, Int32, - UInt32, Int64, UInt64, Float32, Float64, - Char, Byte, String, Nil, TypeKind.Nan, Bool, - TypeKind.Inf, Auto, Pointer, Reference, Any, - Union, Generic: - result &= self.stringify(typ.value) - of Function: - result &= "fn (" - for i, (argName, argType, argDefault) in typ.value.arguments: - result &= &"{argName}: {self.stringify(argType)}" - if not argDefault.isNil(): - result &= &" = {argDefault}" - if i < typ.value.arguments.len() - 1: - result &= ", " - result &= ")" - if not typ.value.returnType.isNil(): - result &= &": {self.stringify(typ.value.returnType)}" - var node = Declaration(typ.node) - if node.pragmas.len() > 0: - result &= " {" - for i, pragma in node.pragmas: - result &= &"{pragma.name.token.lexeme}" - if pragma.args.len() > 0: - result &= ": " - for j, arg in pragma.args: - result &= arg.token.lexeme - if j < pragma.args.high(): - result &= ", " - if i < node.pragmas.high(): - result &= ", " - else: - result &= "}" - else: - discard - - -proc findByName*(self: Compiler, name: string): seq[Name] = - ## Looks for objects that have been already declared - ## with the given name. Returns all objects that apply. - for obj in reversed(self.names): - if obj.ident.token.lexeme == name: - if obj.owner.path != self.currentModule.path: - if obj.isPrivate or self.currentModule notin obj.exports: - continue - result.add(obj) - - -proc findInModule*(self: Compiler, name: string, module: Name): seq[Name] = - ## Looks for objects that have been already declared as - ## public within the given module with the given name. - ## Returns all objects that apply. If the name is an - ## empty string, returns all objects within the given - ## module, regardless of whether they are exported to - ## the current one or not - if name == "": - for obj in reversed(self.names): - if not obj.isPrivate and obj.owner == module: - result.add(obj) - else: - for obj in self.findInModule("", module): - if obj.ident.token.lexeme == name and self.currentModule in obj.exports: - result.add(obj) - - -proc findByType*(self: Compiler, name: string, kind: Type): seq[Name] = - ## Looks for objects that have already been declared - ## with the given name and type. Returns all objects - ## that apply - for name in self.findByName(name): - if self.compare(name.obj.value, kind): - result.add(name) - - -proc findAtDepth*(self: Compiler, name: string, depth: int): seq[Name] {.used.} = - ## Looks for objects that have been already declared - ## with the given name at the given scope depth. - ## Returns all objects that apply - for obj in self.findByName(name): - if obj.depth == depth: - result.add(obj) - - -proc check*(self: Compiler, term: Expression, kind: Type) {.inline.} = - ## Checks the type of term against a known type. - ## Raises an error if appropriate and returns - ## otherwise - let k = self.inferOrError(term) - if not self.compare(k.value, kind): - self.error(&"expecting value of type {self.stringify(kind)}, got {self.stringify(k)}", term) - elif k.value.kind == Any and kind.kind != Any: - self.error(&"any is not a valid type in this context") - - -proc isAny*(typ: Type): bool = - ## Returns true if the given type is - ## of (or contains) the any type - case typ.kind: - of Any: - return true - of Generic: - for condition in typ.constraints: - if condition.kind.isAny(): - return true - of Union: - for condition in typ.types: - if condition.kind.isAny(): - return true - else: - return false - - -proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allowFwd: bool = true): Name = - ## Tries to find a matching function implementation - ## compatible with the given type and returns its - ## name object - var impl: seq[Name] = self.findByType(name, kind) - if impl.len() == 0: - let names = self.findByName(name) - var msg = &"failed to find a suitable implementation for '{name}'" - if names.len() > 0: - msg &= &", found {len(names)} potential candidate" - if names.len() > 1: - msg &= "s" - if self.showMismatches: - msg &= ":" - for name in names: - msg &= &"\n - in {relativePath(name.file, getCurrentDir())}:{name.ident.token.line}:{name.ident.token.relPos.start} -> {self.stringify(name.obj.value)}" - if name.obj.value.kind != Function: - msg &= ": not a callable" - elif kind.arguments.len() != name.obj.value.arguments.len(): - msg &= &": wrong number of arguments (expected {name.obj.value.arguments.len()}, got {kind.arguments.len()})" - else: - for i, arg in kind.arguments: - if not self.compare(arg.kind, name.obj.value.arguments[i].kind): - msg &= &": first mismatch at position {i + 1}: (expected {self.stringify(name.obj.value.arguments[i].kind)}, got {self.stringify(arg.kind)})" - break - else: - msg &= " (compile with --showMismatches for more details)" - else: - msg = &"call to undefined function '{name}'" - self.error(msg, node) - elif impl.len() > 1: - # If we happen to find more than one match, we try again - # and ignore forward declarations and automatic functions - impl = filterIt(impl, not it.obj.value.forwarded and not it.obj.value.isAuto) - if impl.len() > 1: - # If there's *still* more than one match, then it's an error - var msg = &"multiple matching implementations of '{name}' found" - if self.showMismatches: - msg &= ":" - for fn in reversed(impl): - msg &= &"\n- in {relativePath(fn.file, getCurrentDir())}, line {fn.line} of type {self.stringify(fn.obj.value)}" - else: - msg &= " (compile with --showMismatches for more details)" - self.error(msg, node) - # This is only true when we're called by self.patchForwardDeclarations() - if impl[0].obj.value.forwarded and not allowFwd: - self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].obj.value)}'") - result = impl[0] - for (a, b) in zip(result.obj.value.arguments, kind.arguments): - 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).value)) - 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).value)) - 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 dispatchPragmas(self: Compiler, name: Name) = discard -proc dispatchDelayedPragmas(self: Compiler, name: Name) = discard - - -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, - obj: nil, # Done later - isLet: node.isLet, - line: node.token.line, - belongsTo: self.currentFunction, - kind: DeclType, - 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, - obj: TypedNode(node: node, - value: Type(kind: Function, - returnType: nil, # We check it later - arguments: @[], - forwarded: node.body.isNil(), - isAuto: false) - ), - ident: node.name, - node: node, - isLet: false, - line: node.token.line, - kind: DeclType, - belongsTo: self.currentFunction, - isReal: true) - if node.generics.len() > 0: - n.isGeneric = true - var typ: Type - for argument in node.arguments: - typ = self.infer(argument.valueType).value - if not typ.isNil() and typ.kind == Auto: - n.obj.value.isAuto = true - if n.isGeneric: - self.error("automatic types cannot be used within generics", argument.valueType) - break - typ = self.infer(node.returnType).value - if not typ.isNil() and typ.kind == Auto: - n.obj.value.isAuto = true - if n.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 = ast.TypeDecl(node) - self.names.add(Name(kind: DeclType, - depth: self.depth, - owner: self.currentModule, - node: node, - ident: node.name, - line: node.token.line, - isPrivate: node.isPrivate, - isReal: true, - belongsTo: self.currentFunction, - obj: TypedNode(node: node, value: Type(kind: TypeDecl)) - ) - ) - n = self.names[^1] - declaredName = node.name.token.lexeme - if node.value.isNil(): - discard # TODO: Fields - else: - case node.value.kind: - of identExpr: - n.obj.value = self.inferOrError(node.value).value - of binaryExpr: - # Type union - n.obj.value = Type(kind: Union, types: @[]) - self.unpackUnion(node.value, n.obj.value.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 == DeclType 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})") - # We emit a bunch of warnings, mostly for QoL - 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.exports): - continue - if name.kind == DeclType: - if name.depth < n.depth: - self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer scope ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n) - if name.owner != n.owner: - self.warning(WarningKind.ShadowOuterScope, &"'{declaredName}' at depth {name.depth} shadows a name from an outer module ({name.owner.file}.pn:{name.ident.token.line}:{name.ident.token.relPos.start})", n) - return n diff --git a/src/frontend/compiler/targets/bytecode/target.nim b/src/frontend/compiler/targets/bytecode/target.nim index a325466..79c73a8 100644 --- a/src/frontend/compiler/targets/bytecode/target.nim +++ b/src/frontend/compiler/targets/bytecode/target.nim @@ -1172,9 +1172,13 @@ method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Typ returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default)]) var impl = self.match(node.token.lexeme, fn, node) - if impl.valueType.isAuto: + result = impl.valueType + if impl.isGeneric: + result = self.specialize(impl.valueType, @[node.a]) + elif impl.valueType.isAuto: impl = self.prepareAutoFunction(impl, fn.args) - result = impl.valueType.returnType + result = impl.valueType + result = result.returnType if compile: self.generateCall(impl, @[node.a], impl.line) @@ -1184,9 +1188,13 @@ method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): T var default: Expression let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)]) var impl = self.match(node.token.lexeme, fn, node) - if impl.valueType.isAuto: + result = impl.valueType + if impl.isGeneric: + result = self.specialize(impl.valueType, @[node.a, node.b]) + elif impl.valueType.isAuto: impl = self.prepareAutoFunction(impl, fn.args) - result = impl.valueType.returnType + result = impl.valueType + result = result.returnType if compile: self.generateCall(impl, @[node.a, node.b], impl.line) @@ -1305,7 +1313,9 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type # Calls like hi() var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node) result = impl.valueType - if impl.valueType.isAuto: + if impl.isGeneric: + result = self.specialize(impl.valueType, argExpr) + elif impl.valueType.isAuto: impl = self.prepareAutoFunction(impl, args) result = impl.valueType if result.fun.kind == NodeKind.lambdaExpr: @@ -1350,13 +1360,20 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type result = result.returnType of NodeKind.getItemExpr: var node = GetItemExpr(node.callee) - let impl = self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))) + result = self.getItemExpr(node, compile=false, matching=Type(kind: Function, args: args, returnType: Type(kind: All))) var fn: Name + # getItemExpr returns a Type object, but + # we need a Name object! for name in self.names: - if name.valueType == impl: + if name.valueType == result: fn = name break - result = impl.returnType + if fn.isGeneric: + result = self.specialize(result, argExpr) + elif result.isAuto: + fn = self.prepareAutoFunction(fn, args) + result = fn.valueType + result = result.returnType if compile: self.generateCall(fn, argExpr, node.token.line) of NodeKind.lambdaExpr: @@ -1393,7 +1410,7 @@ method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = tr for name in values: if self.compare(name.valueType, matching): result = name.valueType - break + return if len(values) == 1: result = values[0].valueType else: @@ -1687,14 +1704,14 @@ proc importStmt(self: BytecodeCompiler, node: ImportStmt, compile: bool = true) # Importing a module automatically exports # its public names to us for name in self.findInModule("", module): - name.exportedTo.incl(self.currentModule.path) + name.exportedTo.add(self.currentModule) # We also need to export public names from other modules # that we have explicitly exported because imports are # compiled only once for module in self.modules.values(): - if self.currentModule.path in module.exportedTo: + if self.currentModule in module.exportedTo: for name in self.findInModule("", module): - name.exportedTo.incl(self.currentModule.path) + name.exportedTo.add(self.currentModule) except IOError: self.error(&"could not import '{module.ident.token.lexeme}': {getCurrentExceptionMsg()}") except OSError: @@ -1712,20 +1729,20 @@ proc exportStmt(self: BytecodeCompiler, node: ExportStmt, compile: bool = true) var name = self.resolveOrError(node.name) if name.isPrivate: self.error("cannot export private names") - name.exportedTo.incl(self.parentModule.path) + name.exportedTo.add(self.parentModule) case name.kind: of NameKind.Module: # We need to export everything # this module defines! for name in self.findInModule("", name): - name.exportedTo.incl(self.parentModule.path) + name.exportedTo.add(self.parentModule) of NameKind.Function: # Only exporting a single function (or, well # all of its implementations) for name in self.findByName(name.ident.token.lexeme): if name.kind != NameKind.Function: continue - name.exportedTo.incl(self.parentModule.path) + name.exportedTo.add(self.parentModule) else: self.error("unsupported export type") @@ -2097,19 +2114,28 @@ proc compileModule(self: BytecodeCompiler, module: Name) = ## Compiles an imported module into an existing chunk ## using the compiler's internal parser and lexer objects var path = "" - var moduleName = module.path & ".pn" + let moduleName = module.path & ".pn" + # We take the absolute path of the module so that we + # know that if it's in self.modules, then we already + # imported it for i, searchPath in moduleLookupPaths: if searchPath == "": path = absolutePath(joinPath(splitPath(self.file).head, moduleName)) else: - path = joinPath(searchPath, moduleName) + path = absolutePath(joinPath(searchPath, moduleName)) if fileExists(path): break elif i == searchPath.high(): self.error(&"""could not import '{path}': module not found""") - if self.modules.hasKey(module.path): + module.absPath = path + if self.modules.hasKey(path): + # Module is already imported: we have + # already compiled it return let source = readFile(path) + # Preserve the current state so we can + # resume compiling the current module + # later let current = self.current let ast = self.ast let file = self.file @@ -2120,15 +2146,23 @@ proc compileModule(self: BytecodeCompiler, module: Name) = let parentModule = self.parentModule let replMode = self.replMode self.replMode = false + # Set the current module to the new module + # and the current module as the parent module: + # this is needed for export statements self.parentModule = currentModule self.currentModule = module + # We remember where the new module starts, but + # we don't emit the bytes into the chunk right + # away because we may call this function again + # from within this call and it would break all + # sorts of things let start = self.chunk.code.len() discard self.compile(self.parser.parse(self.lexer.lex(source, path), path, self.lexer.getLines(), self.lexer.getSource(), persist=true), path, self.lexer.getLines(), self.lexer.getSource(), chunk=self.chunk, incremental=true, isMainModule=false, self.disabledWarnings, self.showMismatches, self.mode) - # Mark the end of a new module + # Mark the boundaries of the module self.chunk.modules.extend(start.toTriple()) self.chunk.modules.extend(self.chunk.code.high().toTriple()) # I swear to god if someone ever creates a peon module with a name that's @@ -2148,4 +2182,4 @@ proc compileModule(self: BytecodeCompiler, module: Name) = self.replMode = replMode self.lines = lines self.source = src - self.modules[module.path] = module + self.modules[module.absPath] = module