Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: formatted error reporting #6

Merged
merged 4 commits into from Dec 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Expand Up @@ -11,6 +11,9 @@ only-test:
@cd src; ../node_modules/.bin/ncc build index.ts -o ../dist
@./node_modules/.bin/mocha -r ts-node/register test/\*\*/\*.spec.ts

watch-tests:
./node_modules/.bin/lys test/test.lys --test --wast --lib node_modules/lys/dist/utils/libs/env.js --desugar --watch

only-snapshot: export UPDATE_AST=true
only-snapshot: only-test

Expand Down
217 changes: 216 additions & 1 deletion src/compiler/context.lys
@@ -1 +1,216 @@
struct CompilerContext()
import src::compiler::messagecollector
import src::compiler::nodes
import src::parser::parser
import src::helpers
import src::stringbuilder
import src::compiler::linemapper

enum ModuleMap {
EmptyModule
Module(
moduleName: string,
path: string,
source: string,
document: CodeNode,
phase: i32,
errors: i32,
lineMapper: LineMapper
)
ModuleCons(head: Module, tail: ModuleMap)
}

struct CompilerContext(
messageCollector: MessageCollector,
modules: ModuleMap
)

impl ModuleMap {
#[method]
fun findModuleByPath(self: ModuleMap, path: string): Module | EmptyModule = {
match self {
case x is EmptyModule -> x
case theModule is Module ->
if (theModule.path == path)
theModule
else
EmptyModule
case is ModuleCons(head, tail) ->
if (head.path == path)
head
else
tail.findModuleByPath(path)
}
}

#[method]
fun findModule(self: ModuleMap, moduleName: string): Module | EmptyModule = {
match self {
case x is EmptyModule -> x
case theModule is Module ->
if (theModule.moduleName == moduleName)
theModule
else
EmptyModule
case is ModuleCons(head, tail) ->
if (head.moduleName == moduleName)
head
else
tail.findModule(moduleName)
}
}
}

impl CompilerContext {
fun apply(): CompilerContext = CompilerContext(MessageCollector(), EmptyModule)

#[method]
fun getModuleByContent(self: CompilerContext, path: string, moduleName: string, source: string): Module = {
val ast = parse(source, "Document", src::parser::lysgrammar::getGrammar())

val module = match ast {
case is Nil -> {
self.messageCollector.append("Error parsing file: " ++ path, SourcePosition(path, 0x0, 0x1))
Module(moduleName, path, source, EmptyNode, 0, 1, LineMapper(source))
}
case ast is AstNode -> {
val errors = collectErrors(ast, self.messageCollector, path)
val code = src::compiler::phases::cannonical::processNode(ast)
Module(moduleName, path, source, code, 0, errors, LineMapper(source))
}
}

match self.modules {
case is EmptyModule -> self.modules = module
else -> self.modules = ModuleCons(module, self.modules)
}

module
}

#[method]
fun findModule(self: CompilerContext, moduleName: string): Module | EmptyModule =
self.modules.findModule(moduleName)

#[method]
fun findModuleByPath(self: CompilerContext, path: string): Module | EmptyModule =
self.modules.findModuleByPath(path)

#[method]
fun printErrors(self: CompilerContext, head: Message, sb: StringBuilder, path: string, counter: i32): i32 = match head {
case is PositionCapableMessage(message, position) -> {
if (position.path == path) {
var module = self.findModuleByPath(path)

match module {
case module is Module -> {
val start = module.lineMapper.getLine(position.start)
val end = module.lineMapper.getLine(position.end)

sb.append(" ")
.append(string.stringify(counter + 1))
.append(") ")
.append(message)
.append(" at ")
.append(position.path)
.append(":")
.append(string.stringify(start.line + 0x1))
.append(":")
.append(string.stringify(position.start - start.start))
.append("\n")

if (start.line == end.line) {

sb
.append("\u001b[36m") // Gutter start
.append(" │")
.append("\u001b[0m\n") // reset

.append("\u001b[36m") // Gutter start

val loc = (start.line + 0x1) as i32

if (loc < 10000) { sb.append(" ") }
if (loc < 1000) { sb.append(" ") }
if (loc < 100) { sb.append(" ") }
if (loc < 10) { sb.append(" ") }

sb.append(string.stringify(loc))
.append("│ ")
.append("\u001b[0m") // reset
.append(module.source.substring(start.start as i32, start.end as i32))
.append("\n")

// TODO: trim trailing newline in the line source

sb.append("\u001b[36m") // Gutter start
.append(" │ ")
.append("\u001b[0m") // reset
.append(repeat(" ", (position.start - start.start) as i32))
.append("\u001b[91m") // red color
.append(repeat("^", (position.end - position.start) as i32))
.append("\u001b[0m") // reset
.append("\n")
}

sb.append("\n")

}
else -> {
sb.append(string.stringify(counter + 1))
.append(") ")
.append(message)
.append(" at ")
.append(position.path)
.append("\n")
}
}

counter + 1
} else {
counter
}
}
case is MessageCons(head, tail) -> {
// Print the first errors, return the current number
val newNumber = printErrors(self, tail, sb, path, counter)
// Print the rest of the errors
printErrors(self, head, sb, path, newNumber)
}
else -> counter
}

#[method]
fun printErrors(self: CompilerContext, sb: StringBuilder): i32 = {
var errors = 0
var current = self.modules

loop {
match current {
case is ModuleCons(head, tail) -> {
val nsb = StringBuilder()
errors = printErrors(self, self.messageCollector.headMessage, nsb, head.path, errors)

if (!nsb.isEmpty()) {
sb.append(head.path).append("\n").append(nsb.toString())
}

current = tail
continue
}
case head is Module -> {
val nsb = StringBuilder()
errors = printErrors(self, self.messageCollector.headMessage, sb, head.path, errors)

if (!nsb.isEmpty()) {
sb.append(head.path).append("\n").append(nsb.toString())
}

break
}
case is EmptyModule -> break
}
}

errors
}
}