feat: sonya now writes out more comprehensible error messages

This commit is contained in:
2026-04-23 21:52:26 +09:00
parent eb851e49fa
commit 354186bc33
4 changed files with 50 additions and 32 deletions
+7 -8
View File
@@ -17,21 +17,20 @@ struct sonya {
case .isEmpty:
print("sonya: sonyafile is empty")
exit(2)
case .syntaxError:
print("sonya: syntax error in sonyafile")
case .syntaxError(let errs):
for err in errs {
print("line '\(err)' is unrecognized")
}
exit(3)
case .noSuchRecipe:
print("sonya: no recipe named '\(recipe)'")
exit(4)
case .noSuchDependency:
print("sonya: unknown dependency")
case .shellReturnedError(let code):
print("sonya: command failed with code \(code)")
exit(5)
case .shellReturnedError:
print("sonya: command failed")
exit(6)
case .noSuchVariable:
print("sonya: undefined variable referenced in recipe '\(recipe)'")
exit(7)
exit(6)
}
}
}
@@ -7,14 +7,13 @@
import Foundation
public enum runResult {
public enum runResult: Equatable {
case OK
case noSonyafile
case isEmpty
case syntaxError
case syntaxError(errs: [String])
case noSuchRecipe
case noSuchDependency
case shellReturnedError
case shellReturnedError(Int32)
case noSuchVariable
}
@@ -24,8 +23,9 @@ public func runRecipe(_ recipeName: String, args: [String] = []) -> runResult {
let tokenized = Lexer().tokenize(sonyafile)
guard !tokenized.isEmpty else { return .isEmpty }
guard let ast = Parser().parse(tokenized) else { return .syntaxError }
// guard let ast = Parser().parse(tokenized) else { return .syntaxError }
switch Parser().parse(tokenized) {
case .success(let ast):
guard let requestedRecipe = ast.rules[recipeName] else { return .noSuchRecipe }
// check for dependencies
if !requestedRecipe.dependencies.isEmpty {
@@ -38,9 +38,13 @@ public func runRecipe(_ recipeName: String, args: [String] = []) -> runResult {
for command in requestedRecipe.commands {
guard let resolved = resolve(command, vars: ast.vars) else { return .noSuchVariable }
guard shell(resolved) == 0 else { return .shellReturnedError }
let exitCode = shell(resolved)
guard exitCode == 0 else { return .shellReturnedError(exitCode) }
}
// all good
return .OK
case .failure(let error):
return .syntaxError(errs: error.errorLines)
}
}
@@ -7,6 +7,7 @@ enum Token : Equatable {
case variable(name: String, value: String)
case comment(String)
case newline
case error(line: String)
}
struct Lexer {
@@ -75,7 +76,7 @@ struct Lexer {
return [.command(String(match.1))]
}
return [] // unrecognized
return [.error(line: line)] // unrecognized
}
}
+17 -3
View File
@@ -1,3 +1,11 @@
struct errorLines: Error {
var errorLines: [String]
init(_ errs: [String]) {
errorLines = errs
}
}
struct sonyaAST {
var vars: [String : String] = [:]
var rules: [String : Rule] = [:]
@@ -9,8 +17,9 @@ struct Rule {
}
struct Parser {
func parse(_ tokens: [Token]) -> sonyaAST? {
func parse(_ tokens: [Token]) -> Result<sonyaAST, errorLines> {
var ast = sonyaAST()
var errors: [String] = []
var currentRule: (name: String, rule: Rule)? = nil
for token in tokens {
@@ -26,6 +35,8 @@ struct Parser {
currentRule?.rule.dependencies.append(name)
case .command(let cmd):
currentRule?.rule.commands.append(cmd)
case .error(let line):
errors.append(line)
case .newline, .comment:
continue
}
@@ -33,7 +44,10 @@ struct Parser {
if let rule = currentRule { ast.rules[rule.name] = rule.rule }
currentRule = nil
return ast
if errors.isEmpty {
return .success(ast)
} else {
return .failure(errorLines(errors))
}
}
}