Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ Start [bootstraping the compiler](https://en.wikipedia.org/wiki/Bootstrapping_(c

[Blog post](https://menduz.com/posts/2019.12.26)

## To build locally
## Watch changes

Run `make build`, make sure to have at least Node.js 12 installed.
There are two ways to watch this project:
1) watch the sources: `make watch`.
2) watch the sources + tests, run tests on changes: `make watch-tests`.

1. It first builds Lys to the [build](build) folder using `lys src/main.lys --wast`
2. It then uses `@zeit/ncc` to create a single file using [src/index.ts](src/index.ts)
3. Finally, it performs a [sanity test](test.js) using mocha.
## Testing locally

Most of the testing is based on golden files, located in the folder [test/fixtures](test/fixtures). Those lys files are parsed and depending on the test some other files are emited and compared against the files in the file system; if it is different the test will fail.

To update the golden files from a dry run, execute `make snapshot`.
To update the golden files while watching execute `make only-snapshot`.

> Note: to run `make only-snapshot` it is necessary to run `make watch` in parallel.

## How to build

Run `make build`, make sure to have at least Node.js 12 installed. It will install, compile and run the full test suite.
25,011 changes: 25,011 additions & 0 deletions failed_debug_wat.wat

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/compiler/context.lys
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ impl CompilerContext {
case ast is AstNode -> {
val errors = collectErrors(ast, self.messageCollector, path)
val newAst = src::compiler::phases::transformation::runPhase(path, moduleName, ast, self)
val code = src::compiler::phases::cannonical::processNode(newAst)
src::compiler::phases::semantic::runPhase(path, moduleName, code, self)
var code = src::compiler::phases::cannonical::processNode(newAst)
code = src::compiler::phases::scopecreation::runPhase(path, moduleName, code, self)
code = src::compiler::phases::semantic::runPhase(path, moduleName, code, self)
Module(moduleName, path, source, newAst, code, 0, errors, LineMapper(source))
}
}
Expand Down
21 changes: 19 additions & 2 deletions src/compiler/nodes.lys
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import src::helpers
import src::parser::parser
import src::compiler::annotations
import src::stringbuilder
import src::compiler::scope

struct NodeMeta(astNode: AstNode, annotations: NodeAnnotation)
struct NodeMeta(astNode: AstNode, annotations: NodeAnnotation, scope: NodeScope)

impl NodeMeta {
fun apply(astNode: AstNode): NodeMeta = NodeMeta(astNode, NoAnnotation)
fun apply(astNode: AstNode): NodeMeta = NodeMeta(astNode, NoAnnotation, NoScope)

#[method]
fun annotate(self: NodeMeta, annotation: NodeAnnotation): NodeMeta = {
Expand Down Expand Up @@ -258,6 +259,22 @@ impl CodeNode {
}
}

#[getter]
fun scope(self: CodeNode): NodeScope =
match self.meta {
case is NodeMeta(scope) -> scope
else -> NoScope
}

#[setter]
fun scope(self: CodeNode, scope: NodeScope): void =
match self.meta {
case meta is NodeMeta -> {
meta.scope = scope
}
else -> { /* void */ }
}

fun printWithSeparator(self: CodeNode, sb: StringBuilder, indentation: i32, separator: string): i32 = {
printWithSeparator(self, sb, indentation, separator, 0)
}
Expand Down
201 changes: 201 additions & 0 deletions src/compiler/nodewalker.lys
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import src::compiler::nodes
import src::compiler::context

fun noop(node: CodeNode, context: CompilerContext, parent: CodeNode): void = {
// noop
}

fun walk(node: CodeNode, context: CompilerContext, parent: CodeNode, onEnter: fun(node: CodeNode, context: CompilerContext, parent: CodeNode) -> void, onLeave: fun(node: CodeNode, context: CompilerContext, parent: CodeNode) -> void): void = {
onEnter(node, context, parent)

match node {
case is CodeNodeCons(head, tail) -> {
walk(head, context, node, onEnter, onLeave)
walk(tail, context, node, onEnter, onLeave)
}
case is Document(headDirective) -> {
walk(headDirective, context, node, onEnter, onLeave)
}
case is FunctionTypeNode(parameters, returnType, effect) -> {
walk(parameters, context, node, onEnter, onLeave)
walk(returnType, context, node, onEnter, onLeave)
walk(effect, context, node, onEnter, onLeave)
}
case is QNameNode(names) -> {
walk(names, context, node, onEnter, onLeave)
}
case is ReferenceNode(variable) -> {
walk(variable, context, node, onEnter, onLeave)
}
case is BlockNode(headStatement) -> {
walk(headStatement, context, node, onEnter, onLeave)
}
case is MemberNode(lhs, operator, rhs) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is DecoratorNode(decoratorName, headArgument) -> {
walk(decoratorName, context, node, onEnter, onLeave)
walk(headArgument, context, node, onEnter, onLeave)
}
case is ParameterNode(parameterName, parameterType) -> {
walk(parameterName, context, node, onEnter, onLeave)
walk(parameterType, context, node, onEnter, onLeave)
}
case is FunctionNode(functionName, headParameter, returnType, body) -> {
walk(functionName, context, node, onEnter, onLeave)
walk(headParameter, context, node, onEnter, onLeave)
walk(returnType, context, node, onEnter, onLeave)
walk(body, context, node, onEnter, onLeave)
}
case is NameLiteralPairNode(name, value) -> {
walk(name, context, node, onEnter, onLeave)
walk(value, context, node, onEnter, onLeave)
}
case is StackLiteralNode(headNames) -> {
walk(headNames, context, node, onEnter, onLeave)
}
case is VarDeclarationNode(name, typeDecl, value) -> {
walk(name, context, node, onEnter, onLeave)
walk(typeDecl, context, node, onEnter, onLeave)
walk(value, context, node, onEnter, onLeave)
}
case is AssignmentNode(lhs, rhs) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is ImplDirectiveNode(baseImpl, targetImpl, headDirective) -> {
walk(baseImpl, context, node, onEnter, onLeave)
walk(targetImpl, context, node, onEnter, onLeave)
walk(headDirective, context, node, onEnter, onLeave)
}
case is ImportDirectiveNode(module, alias) -> {
walk(module, context, node, onEnter, onLeave)
walk(alias, context, node, onEnter, onLeave)
}
case is StructDirectiveNode(modifier, decl) -> {
walk(modifier, context, node, onEnter, onLeave)
walk(decl, context, node, onEnter, onLeave)
}
case is FunDirectiveNode(headDecorator, modifier, function) -> {
walk(headDecorator, context, node, onEnter, onLeave)
walk(modifier, context, node, onEnter, onLeave)
walk(function, context, node, onEnter, onLeave)
}
case is EffectDirectiveNode(effectDecl) -> {
walk(effectDecl, context, node, onEnter, onLeave)
}
case is OverloadedFunDirectiveNode(functionName, headFun) -> {
walk(functionName, context, node, onEnter, onLeave)
walk(headFun, context, node, onEnter, onLeave)
}
case is VarDirectiveNode(modifier, decl) -> {
walk(modifier, context, node, onEnter, onLeave)
walk(decl, context, node, onEnter, onLeave)
}
case is TypeDirectiveNode(modifier, name, declType) -> {
walk(modifier, context, node, onEnter, onLeave)
walk(name, context, node, onEnter, onLeave)
walk(declType, context, node, onEnter, onLeave)
}
case is TraitDirectiveNode(modifier, name, headDirective) -> {
walk(modifier, context, node, onEnter, onLeave)
walk(name, context, node, onEnter, onLeave)
walk(headDirective, context, node, onEnter, onLeave)
}
case is EnumDirectiveNode(modifier, name, headDeclaration) -> {
walk(modifier, context, node, onEnter, onLeave)
walk(name, context, node, onEnter, onLeave)
walk(headDeclaration, context, node, onEnter, onLeave)
}

case is StructTypeNode(headParameter) -> {
walk(headParameter, context, node, onEnter, onLeave)
}

case is AbstractFunctionCallNode(headArgument, resolvedFunction) -> {
walk(headArgument, context, node, onEnter, onLeave)
}
case is InjectedFunctionCallNode(headArgument, resolvedFunction) -> {
walk(headArgument, context, node, onEnter, onLeave)
}
case is FunctionCallNode(functionNode, headArgument, resolvedFunction) -> {
walk(functionNode, context, node, onEnter, onLeave)
walk(headArgument, context, node, onEnter, onLeave)
}
case is ParenExpressionNode(expression) -> {
walk(expression, context, node, onEnter, onLeave)
}
case is BinaryExpressionNode(lhs, operator, rhs) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(operator, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is AsExpressionNode(lhs, rhs) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is IsExpressionNode(lhs, rhs) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is UnaryExpressionNode(operator, rhs) -> {
walk(operator, context, node, onEnter, onLeave)
walk(rhs, context, node, onEnter, onLeave)
}
case is WasmAtomNode(symbol, headArgument) -> {
walk(headArgument, context, node, onEnter, onLeave)
}
case is WasmExpressionNode(headAtom) -> {
walk(headAtom, context, node, onEnter, onLeave)
}
case is IfNode(condition, truePart, falsePart) -> {
walk(condition, context, node, onEnter, onLeave)
walk(truePart, context, node, onEnter, onLeave)
walk(falsePart, context, node, onEnter, onLeave)
}
case is UnionTypeNode(headType) -> {
walk(headType, context, node, onEnter, onLeave)
}
case is IntersectionTypeNode(headType) -> {
walk(headType, context, node, onEnter, onLeave)
}
case is StructDeclarationNode(name, headParameter) -> {
walk(name, context, node, onEnter, onLeave)
walk(headParameter, context, node, onEnter, onLeave)
}
case is EffectDeclarationNode(name) -> {
walk(name, context, node, onEnter, onLeave)
}
case is PatternMatcherNode(lhs, headMatcher) -> {
walk(lhs, context, node, onEnter, onLeave)
walk(headMatcher, context, node, onEnter, onLeave)
}
case is MatchConditionNode(declaredName, condition, body) -> {
walk(declaredName, context, node, onEnter, onLeave)
walk(condition, context, node, onEnter, onLeave)
walk(body, context, node, onEnter, onLeave)
}
case is MatchCaseIsNode(declaredName, typeReference, headDeconstruct, body) -> {
walk(declaredName, context, node, onEnter, onLeave)
walk(typeReference, context, node, onEnter, onLeave)
walk(headDeconstruct, context, node, onEnter, onLeave)
walk(body, context, node, onEnter, onLeave)
}
case is MatchLiteralNode(literal, body) -> {
walk(literal, context, node, onEnter, onLeave)
walk(body, context, node, onEnter, onLeave)
}
case is MatchDefaultNode(declaredName, body) -> {
walk(declaredName, context, node, onEnter, onLeave)
walk(body, context, node, onEnter, onLeave)
}
case is LoopNode(expression) -> {
walk(expression, context, node, onEnter, onLeave)
}

else -> { /* no op */ }
}

onLeave(node, context, parent)
}
60 changes: 60 additions & 0 deletions src/compiler/phases/scopecreation.lys
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import src::compiler::nodes
import src::compiler::annotations
import src::compiler::nodewalker
import src::compiler::context
import src::compiler::scope

fun runPhase(currentPath: string, currentModule: string, node: CodeNode, ctx: CompilerContext): CodeNode = {
walk(node, ctx, EmptyNode, enter, noop)

if (node is Document) {
node.scope = Scope("", currentModule)
}

node
}

fun enter(node: CodeNode, context: CompilerContext, parent: CodeNode): void = {
val parentScope = parent.scope

if (node.scope == NoScope) {
node.scope = parentScope
}

match node.scope {
case scope is Scope -> {
match node {
case is MatchConditionNode(body) -> {
body.scope = scope.newChildScope("MatcherRHS")
}
case is MatchCaseIsNode(body) -> {
body.scope = scope.newChildScope("MatcherRHS")
}
case is MatchLiteralNode(body) -> {
body.scope = scope.newChildScope("MatcherRHS")
}
case is MatchDefaultNode(body) -> {
body.scope = scope.newChildScope("MatcherRHS")
}
case is FunctionNode(body) -> {
body.scope = scope.newChildScope("Body")
}
case is VarDeclarationNode(value) -> {
val varName = "VarName"
value.scope = scope.newChildScope(varName ++ ".")
}
case is TraitDirectiveNode -> {
val traitName = "TraitName"
node.scope = scope.newChildScope(traitName ++ ".")
}
case is ImplDirectiveNode -> {
val implName = "ImplName"
node.scope = scope.newChildScope(implName ++ ".")
}
case is BlockNode -> {
node.scope = scope.newChildScope("Block")
}
}
}
}
}
3 changes: 1 addition & 2 deletions src/compiler/phases/transformation.lys
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ fun appendDestructureDeclaration(varNameIdentifier: AstNode, headReference: AstN
if (referenceName != "_") {
val reference = Node("Reference", Node("QName", varNameIdentifier, 0x0, 0x0), 0x0, 0x0)

val decl = Node("ValDeclaration",
val decl = Node("ValDeclaration",
AstCons(
headReference,
Node("AtomicExpression", AstCons(
Expand All @@ -410,7 +410,6 @@ fun appendDestructureDeclaration(varNameIdentifier: AstNode, headReference: AstN
}
else -> Node("CodeBlock", AstCons(decl, wrappedNode), 0x0, 0x0)
}

} else {
wrappedNode
}
Expand Down
Loading