diff --git a/examples/fib.pork b/examples/fib.pork index 050d712..2f6c80e 100644 --- a/examples/fib.pork +++ b/examples/fib.pork @@ -7,7 +7,7 @@ func fib(n) { else fib(n - 1) + fib(n - 2) } -func main() { +export func main() { result = fib(20) println(result) } diff --git a/examples/imports.pork b/examples/imports.pork index e892222..d7138e6 100644 --- a/examples/imports.pork +++ b/examples/imports.pork @@ -1,5 +1,5 @@ import "module.pork" -func main() { +export func main() { hello() } diff --git a/examples/module.pork b/examples/module.pork index 1d5f3b8..29a0c87 100644 --- a/examples/module.pork +++ b/examples/module.pork @@ -1,3 +1,3 @@ -func hello() { +export func hello() { println("Hello World") } diff --git a/examples/syntax.pork b/examples/syntax.pork index c876612..067b3e0 100644 --- a/examples/syntax.pork +++ b/examples/syntax.pork @@ -1,4 +1,4 @@ -func main() { +export func main() { three = 3 two = 2 diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt index 2a49069..0213749 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt @@ -3,4 +3,7 @@ package gay.pizza.pork.ast.nodes import kotlinx.serialization.Serializable @Serializable -sealed class Definition : Node() +sealed class Definition : Node() { + abstract val symbol: Symbol + abstract val modifiers: DefinitionModifiers +} diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/DefinitionModifiers.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/DefinitionModifiers.kt new file mode 100644 index 0000000..5226749 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/DefinitionModifiers.kt @@ -0,0 +1,8 @@ +package gay.pizza.pork.ast.nodes + +import kotlinx.serialization.Serializable + +@Serializable +class DefinitionModifiers( + var export: Boolean +) diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt index 1f9b4da..530f5b6 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt @@ -7,7 +7,12 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("functionDefinition") -class FunctionDefinition(val symbol: Symbol, val arguments: List, val block: Block) : Definition() { +class FunctionDefinition( + override val modifiers: DefinitionModifiers, + override val symbol: Symbol, + val arguments: List, + val block: Block +) : Definition() { override val type: NodeType = NodeType.FunctionDeclaration override fun visitChildren(visitor: NodeVisitor): List = diff --git a/src/main/kotlin/gay/pizza/pork/cli/FileTool.kt b/src/main/kotlin/gay/pizza/pork/cli/FileTool.kt index 2437257..6f42d84 100644 --- a/src/main/kotlin/gay/pizza/pork/cli/FileTool.kt +++ b/src/main/kotlin/gay/pizza/pork/cli/FileTool.kt @@ -1,5 +1,7 @@ package gay.pizza.pork.cli +import gay.pizza.pork.frontend.ContentSource +import gay.pizza.pork.frontend.FsContentSource import gay.pizza.pork.parse.CharSource import gay.pizza.pork.parse.StringCharSource import java.nio.file.Path @@ -7,6 +9,6 @@ import kotlin.io.path.readText class FileTool(val path: Path) : Tool() { override fun createCharSource(): CharSource = StringCharSource(path.readText()) - override fun resolveImportSource(path: String): CharSource = - StringCharSource(this.path.parent.resolve(path).readText()) + override fun createContentSource(): ContentSource = FsContentSource(path.parent) + override fun rootFilePath(): String = path.fileName.toString() } diff --git a/src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt b/src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt index 81caa98..74a53bf 100644 --- a/src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt +++ b/src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt @@ -3,7 +3,6 @@ package gay.pizza.pork.cli import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.types.path -import gay.pizza.pork.eval.Arguments import gay.pizza.pork.eval.CallableFunction import gay.pizza.pork.eval.Scope @@ -19,6 +18,5 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") { } }) tool.evaluate(scope) - scope.call("main", Arguments(emptyList())) } } diff --git a/src/main/kotlin/gay/pizza/pork/cli/Tool.kt b/src/main/kotlin/gay/pizza/pork/cli/Tool.kt index dda0ff1..7fb4d3d 100644 --- a/src/main/kotlin/gay/pizza/pork/cli/Tool.kt +++ b/src/main/kotlin/gay/pizza/pork/cli/Tool.kt @@ -3,14 +3,15 @@ package gay.pizza.pork.cli import gay.pizza.pork.ast.NodeVisitor import gay.pizza.pork.ast.Printer import gay.pizza.pork.ast.nodes.CompilationUnit -import gay.pizza.pork.eval.Evaluator -import gay.pizza.pork.eval.ImportLoader -import gay.pizza.pork.eval.Scope +import gay.pizza.pork.eval.* +import gay.pizza.pork.frontend.ContentSource +import gay.pizza.pork.frontend.World import gay.pizza.pork.parse.* abstract class Tool { abstract fun createCharSource(): CharSource - abstract fun resolveImportSource(path: String): CharSource + abstract fun createContentSource(): ContentSource + abstract fun rootFilePath(): String fun tokenize(): TokenStream = Tokenizer(createCharSource()).tokenize() @@ -21,17 +22,15 @@ abstract class Tool { fun highlight(scheme: HighlightScheme): List = Highlighter(scheme).highlight(tokenize()) - fun evaluate(scope: Scope = Scope()): Any = - visit(Evaluator(scope, FrontendImportLoader(this))) - fun reprint(): String = buildString { visit(Printer(this)) } fun visit(visitor: NodeVisitor): T = visitor.visit(parse()) - private class FrontendImportLoader(val frontend: Tool) : ImportLoader { - override fun load(path: String): CompilationUnit { - val tokenStream = Tokenizer(frontend.resolveImportSource(path)).tokenize() - return Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution).readCompilationUnit() - } + fun evaluate(scope: Scope) { + val contentSource = createContentSource() + val world = World(contentSource) + val evaluator = Evaluator(world, scope) + val resultingScope = evaluator.evaluate(rootFilePath()) + resultingScope.call("main", Arguments(emptyList())) } } diff --git a/src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt b/src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt new file mode 100644 index 0000000..8d5752a --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt @@ -0,0 +1,30 @@ +package gay.pizza.pork.eval + +import gay.pizza.pork.ast.nodes.CompilationUnit +import gay.pizza.pork.ast.nodes.ImportDeclaration + +class EvaluationContext( + val compilationUnit: CompilationUnit, + val evaluationContextProvider: EvaluationContextProvider, + rootScope: Scope +) { + val internalScope = rootScope.fork() + val externalScope = rootScope.fork() + + private val evaluationVisitor = EvaluationVisitor(internalScope) + + fun setup() { + val imports = compilationUnit.declarations.filterIsInstance() + for (import in imports) { + val evaluationContext = evaluationContextProvider.provideEvaluationContext(import.path.text) + internalScope.inherit(evaluationContext.externalScope) + } + + for (definition in compilationUnit.definitions) { + evaluationVisitor.visit(definition) + if (definition.modifiers.export) { + externalScope.define(definition.symbol.id, internalScope.value(definition.symbol.id)) + } + } + } +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/EvaluationContextProvider.kt b/src/main/kotlin/gay/pizza/pork/eval/EvaluationContextProvider.kt new file mode 100644 index 0000000..6353d8f --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/EvaluationContextProvider.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.eval + +interface EvaluationContextProvider { + fun provideEvaluationContext(path: String): EvaluationContext +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt b/src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt new file mode 100644 index 0000000..46c105a --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt @@ -0,0 +1,143 @@ +package gay.pizza.pork.eval + +import gay.pizza.pork.ast.NodeVisitor +import gay.pizza.pork.ast.nodes.* + +class EvaluationVisitor(val root: Scope) : NodeVisitor { + private var currentScope: Scope = root + + override fun visitIntLiteral(node: IntLiteral): Any = node.value + override fun visitStringLiteral(node: StringLiteral): Any = node.text + override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value + override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) } + + override fun visitSymbol(node: Symbol): Any = None + + override fun visitFunctionCall(node: FunctionCall): Any { + val arguments = node.arguments.map { visit(it) } + return currentScope.call(node.symbol.id, Arguments(arguments)) + } + + override fun visitDefine(node: Assignment): Any { + val value = visit(node.value) + currentScope.define(node.symbol.id, value) + return value + } + + override fun visitSymbolReference(node: SymbolReference): Any = + currentScope.value(node.symbol.id) + + override fun visitLambda(node: Lambda): CallableFunction { + return CallableFunction { arguments -> + currentScope = currentScope.fork() + for ((index, argumentSymbol) in node.arguments.withIndex()) { + currentScope.define(argumentSymbol.id, arguments.values[index]) + } + try { + var value: Any? = null + for (expression in node.expressions) { + value = visit(expression) + } + value ?: None + } finally { + currentScope = currentScope.leave() + } + } + } + + override fun visitParentheses(node: Parentheses): Any = visit(node.expression) + + override fun visitPrefixOperation(node: PrefixOperation): Any { + val value = visit(node.expression) + return when (node.op) { + PrefixOperator.Negate -> { + if (value !is Boolean) { + throw RuntimeException("Cannot negate a value which is not a boolean.") + } + !value + } + } + } + + override fun visitIf(node: If): Any { + val condition = visit(node.condition) + return if (condition == true) { + visit(node.thenExpression) + } else { + if (node.elseExpression != null) { + visit(node.elseExpression) + } else { + None + } + } + } + + override fun visitInfixOperation(node: InfixOperation): Any { + val left = visit(node.left) + val right = visit(node.right) + + when (node.op) { + InfixOperator.Equals -> { + return left == right + } + InfixOperator.NotEquals -> { + return left != right + } + else -> {} + } + + if (left !is Number || right !is Number) { + throw RuntimeException("Failed to evaluate infix operation, bad types.") + } + + val leftInt = left.toInt() + val rightInt = right.toInt() + + return when (node.op) { + InfixOperator.Plus -> leftInt + rightInt + InfixOperator.Minus -> leftInt - rightInt + InfixOperator.Multiply -> leftInt * rightInt + InfixOperator.Divide -> leftInt / rightInt + else -> throw RuntimeException("Unable to handle operation ${node.op}") + } + } + + override fun visitFunctionDeclaration(node: FunctionDefinition): Any { + val blockFunction = visitBlock(node.block) as BlockFunction + val function = CallableFunction { arguments -> + currentScope = currentScope.fork() + for ((index, argumentSymbol) in node.arguments.withIndex()) { + currentScope.define(argumentSymbol.id, arguments.values[index]) + } + try { + return@CallableFunction blockFunction.call() + } finally { + currentScope = currentScope.leave() + } + } + currentScope.define(node.symbol.id, function) + return None + } + + override fun visitBlock(node: Block): Any = BlockFunction { + var value: Any? = null + for (expression in node.expressions) { + value = visit(expression) + } + value ?: None + } + + override fun visitImportDeclaration(node: ImportDeclaration): Any { + throw RuntimeException( + "Import declarations cannot be visited in an EvaluationVisitor. " + + "Utilize an EvaluationContext." + ) + } + + override fun visitCompilationUnit(node: CompilationUnit): Any { + throw RuntimeException( + "Compilation units cannot be visited in an EvaluationVisitor. " + + "Utilize an EvaluationContext." + ) + } +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt index c17ad6e..05eef3d 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt @@ -1,147 +1,22 @@ package gay.pizza.pork.eval -import gay.pizza.pork.ast.NodeVisitor -import gay.pizza.pork.ast.nodes.* +import gay.pizza.pork.frontend.World -class Evaluator(root: Scope, val importLoader: ImportLoader) : NodeVisitor { - private var currentScope: Scope = root +class Evaluator(val world: World, val scope: Scope) : EvaluationContextProvider { + private val contexts = mutableMapOf() - override fun visitIntLiteral(node: IntLiteral): Any = node.value - override fun visitStringLiteral(node: StringLiteral): Any = node.text - override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value - override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) } - - override fun visitSymbol(node: Symbol): Any = None - - override fun visitFunctionCall(node: FunctionCall): Any { - val arguments = node.arguments.map { visit(it) } - return currentScope.call(node.symbol.id, Arguments(arguments)) + fun evaluate(path: String): Scope { + val context = provideEvaluationContext(path) + return context.externalScope } - override fun visitDefine(node: Assignment): Any { - val value = visit(node.value) - currentScope.define(node.symbol.id, value) - return value - } - - override fun visitSymbolReference(node: SymbolReference): Any = - currentScope.value(node.symbol.id) - - override fun visitLambda(node: Lambda): CallableFunction { - return CallableFunction { arguments -> - currentScope = currentScope.fork() - for ((index, argumentSymbol) in node.arguments.withIndex()) { - currentScope.define(argumentSymbol.id, arguments.values[index]) - } - try { - var value: Any? = null - for (expression in node.expressions) { - value = visit(expression) - } - value ?: None - } finally { - currentScope = currentScope.leave() - } + override fun provideEvaluationContext(path: String): EvaluationContext { + val unit = world.load(path) + val identity = world.contentSource.stableContentIdentity(path) + val context = contexts.computeIfAbsent(identity) { + EvaluationContext(unit, this, scope) } - } - - override fun visitParentheses(node: Parentheses): Any = visit(node.expression) - - override fun visitPrefixOperation(node: PrefixOperation): Any { - val value = visit(node.expression) - return when (node.op) { - PrefixOperator.Negate -> { - if (value !is Boolean) { - throw RuntimeException("Cannot negate a value which is not a boolean.") - } - !value - } - } - } - - override fun visitIf(node: If): Any { - val condition = visit(node.condition) - return if (condition == true) { - visit(node.thenExpression) - } else { - if (node.elseExpression != null) { - visit(node.elseExpression) - } else { - None - } - } - } - - override fun visitInfixOperation(node: InfixOperation): Any { - val left = visit(node.left) - val right = visit(node.right) - - when (node.op) { - InfixOperator.Equals -> { - return left == right - } - InfixOperator.NotEquals -> { - return left != right - } - else -> {} - } - - if (left !is Number || right !is Number) { - throw RuntimeException("Failed to evaluate infix operation, bad types.") - } - - val leftInt = left.toInt() - val rightInt = right.toInt() - - return when (node.op) { - InfixOperator.Plus -> leftInt + rightInt - InfixOperator.Minus -> leftInt - rightInt - InfixOperator.Multiply -> leftInt * rightInt - InfixOperator.Divide -> leftInt / rightInt - else -> throw RuntimeException("Unable to handle operation ${node.op}") - } - } - - override fun visitFunctionDeclaration(node: FunctionDefinition): Any { - val blockFunction = visitBlock(node.block) as BlockFunction - val function = CallableFunction { arguments -> - currentScope = currentScope.fork() - for ((index, argumentSymbol) in node.arguments.withIndex()) { - currentScope.define(argumentSymbol.id, arguments.values[index]) - } - try { - return@CallableFunction blockFunction.call() - } finally { - currentScope = currentScope.leave() - } - } - currentScope.define(node.symbol.id, function) - return None - } - - override fun visitBlock(node: Block): Any = BlockFunction { - var value: Any? = null - for (expression in node.expressions) { - value = visit(expression) - } - value ?: None - } - - override fun visitImportDeclaration(node: ImportDeclaration): Any { - val importPath = node.path.text - val compilationUnit = importLoader.load(importPath) - visit(compilationUnit) - return None - } - - override fun visitCompilationUnit(node: CompilationUnit): Any { - for (declaration in node.declarations) { - visit(declaration) - } - - for (definition in node.definitions) { - visit(definition) - } - return None + context.setup() + return context } } diff --git a/src/main/kotlin/gay/pizza/pork/eval/Scope.kt b/src/main/kotlin/gay/pizza/pork/eval/Scope.kt index 14fdb60..f5d1d28 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Scope.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Scope.kt @@ -1,8 +1,14 @@ package gay.pizza.pork.eval -class Scope(val parent: Scope? = null) { +class Scope(val parent: Scope? = null, inherits: List = emptyList()) { + private val inherited = inherits.toMutableList() private val variables = mutableMapOf() + fun has(name: String): Boolean = + variables.containsKey(name) || + (parent?.has(name) ?: false) || + inherited.any { inherit -> inherit.has(name) } + fun define(name: String, value: Any) { if (variables.containsKey(name)) { throw RuntimeException("Variable '${name}' is already defined") @@ -14,7 +20,15 @@ class Scope(val parent: Scope? = null) { val value = variables[name] if (value == null) { if (parent != null) { - return parent.value(name) + if (parent.has(name)) { + return parent.value(name) + } + } + + for (inherit in inherited) { + if (inherit.has(name)) { + return inherit.value(name) + } } throw RuntimeException("Variable '${name}' not defined.") } @@ -39,4 +53,8 @@ class Scope(val parent: Scope? = null) { } return parent } + + internal fun inherit(scope: Scope) { + inherited.add(scope) + } } diff --git a/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt b/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt new file mode 100644 index 0000000..0319262 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt @@ -0,0 +1,8 @@ +package gay.pizza.pork.frontend + +import gay.pizza.pork.parse.CharSource + +interface ContentSource { + fun loadAsCharSource(path: String): CharSource + fun stableContentIdentity(path: String): String +} diff --git a/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt b/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt new file mode 100644 index 0000000..cf11e00 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt @@ -0,0 +1,24 @@ +package gay.pizza.pork.frontend + +import gay.pizza.pork.parse.CharSource +import gay.pizza.pork.parse.StringCharSource +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.readText + +class FsContentSource(val root: Path) : ContentSource { + override fun loadAsCharSource(path: String): CharSource = + StringCharSource(asFsPath(path).readText()) + + override fun stableContentIdentity(path: String): String = + asFsPath(path).absolutePathString() + + private fun asFsPath(path: String): Path { + val fsPath = root.resolve(path) + val absoluteRootPath = root.absolutePathString() + root.fileSystem.separator + if (!fsPath.absolutePathString().startsWith(absoluteRootPath)) { + throw RuntimeException("Unable to load path outside of the root: $fsPath (root is ${root})") + } + return fsPath + } +} diff --git a/src/main/kotlin/gay/pizza/pork/frontend/World.kt b/src/main/kotlin/gay/pizza/pork/frontend/World.kt new file mode 100644 index 0000000..6aa9dde --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/frontend/World.kt @@ -0,0 +1,42 @@ +package gay.pizza.pork.frontend + +import gay.pizza.pork.ast.nodes.CompilationUnit +import gay.pizza.pork.ast.nodes.ImportDeclaration +import gay.pizza.pork.parse.DiscardNodeAttribution +import gay.pizza.pork.parse.Parser +import gay.pizza.pork.parse.TokenStreamSource +import gay.pizza.pork.parse.Tokenizer + +class World(val contentSource: ContentSource) { + private val units = mutableMapOf() + + private fun loadOneUnit(path: String): CompilationUnit { + val stableIdentity = contentSource.stableContentIdentity(path) + val cached = units[stableIdentity] + if (cached != null) { + return cached + } + val charSource = contentSource.loadAsCharSource(path) + val tokenizer = Tokenizer(charSource) + val tokenStream = tokenizer.tokenize() + val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution) + return parser.readCompilationUnit() + } + + private fun resolveAllImports(unit: CompilationUnit): Set { + val units = mutableSetOf() + for (declaration in unit.declarations.filterIsInstance()) { + val importedUnit = loadOneUnit(declaration.path.text) + units.add(importedUnit) + } + return units + } + + fun load(path: String): CompilationUnit { + val unit = loadOneUnit(path) + resolveAllImports(unit) + return unit + } + + fun units(path: String): Set = resolveAllImports(loadOneUnit(path)) +} diff --git a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt index 4a4c61b..2dfd768 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt @@ -175,17 +175,29 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { } private fun readFunctionDeclaration(): FunctionDefinition = within { + val modifiers = DefinitionModifiers(export = false) + while (true) { + val token = peek() + when (token.type) { + TokenType.Export -> { + expect(TokenType.Export) + modifiers.export = true + } + else -> break + } + } expect(TokenType.Func) val name = readSymbolRaw() expect(TokenType.LeftParentheses) val arguments = collect(TokenType.RightParentheses, TokenType.Comma) { readSymbolRaw() } expect(TokenType.RightParentheses) - FunctionDefinition(name, arguments, readBlock()) + FunctionDefinition(modifiers, name, arguments, readBlock()) } private fun maybeReadDefinition(): Definition? { val token = peek() return when (token.type) { + TokenType.Export, TokenType.Func -> readFunctionDeclaration() else -> null } diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt index 291f8ec..1d66cf8 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt @@ -29,6 +29,7 @@ enum class TokenType(vararg properties: TokenTypeProperty) { Then(Keyword("then"), KeywordFamily), Else(Keyword("else"), KeywordFamily), Import(Keyword("import"), KeywordFamily), + Export(Keyword("export"), KeywordFamily), Func(Keyword("func"), KeywordFamily), Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }), BlockComment(CommentFamily),