From 354186bc33b86e97f15503f475329e35c0a0b5c5 Mon Sep 17 00:00:00 2001 From: hwachakarter Date: Thu, 23 Apr 2026 21:52:26 +0900 Subject: [PATCH] feat: sonya now writes out more comprehensible error messages --- Sources/sonya/sonya.swift | 15 +++---- .../sonyalib/Sources/sonyalib/Executor.swift | 44 ++++++++++--------- Sources/sonyalib/Sources/sonyalib/Lexer.swift | 3 +- .../sonyalib/Sources/sonyalib/Parser.swift | 20 +++++++-- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/Sources/sonya/sonya.swift b/Sources/sonya/sonya.swift index 1876b54..3f504ad 100644 --- a/Sources/sonya/sonya.swift +++ b/Sources/sonya/sonya.swift @@ -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) } } } diff --git a/Sources/sonyalib/Sources/sonyalib/Executor.swift b/Sources/sonyalib/Sources/sonyalib/Executor.swift index 3243538..130ee83 100644 --- a/Sources/sonyalib/Sources/sonyalib/Executor.swift +++ b/Sources/sonyalib/Sources/sonyalib/Executor.swift @@ -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,23 +23,28 @@ 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 requestedRecipe = ast.rules[recipeName] else { return .noSuchRecipe } - // check for dependencies - if !requestedRecipe.dependencies.isEmpty { - for dependency in requestedRecipe.dependencies { - let result = runRecipe(dependency) - guard result == .OK else { return result } + // 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 { + for dependency in requestedRecipe.dependencies { + let result = runRecipe(dependency) + guard result == .OK else { return result } + } } - } - - for command in requestedRecipe.commands { - guard let resolved = resolve(command, vars: ast.vars) else { return .noSuchVariable } - guard shell(resolved) == 0 else { return .shellReturnedError } + for command in requestedRecipe.commands { + guard let resolved = resolve(command, vars: ast.vars) else { return .noSuchVariable } + + let exitCode = shell(resolved) + guard exitCode == 0 else { return .shellReturnedError(exitCode) } + } + + // all good + return .OK + case .failure(let error): + return .syntaxError(errs: error.errorLines) } - - // all good - return .OK } diff --git a/Sources/sonyalib/Sources/sonyalib/Lexer.swift b/Sources/sonyalib/Sources/sonyalib/Lexer.swift index d1f53ea..cf8c1e1 100644 --- a/Sources/sonyalib/Sources/sonyalib/Lexer.swift +++ b/Sources/sonyalib/Sources/sonyalib/Lexer.swift @@ -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 } } diff --git a/Sources/sonyalib/Sources/sonyalib/Parser.swift b/Sources/sonyalib/Sources/sonyalib/Parser.swift index adb0f4d..a3435c1 100644 --- a/Sources/sonyalib/Sources/sonyalib/Parser.swift +++ b/Sources/sonyalib/Sources/sonyalib/Parser.swift @@ -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 { 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)) + } } }