feat: sonya now writes out more comprehensible error messages
This commit is contained in:
@@ -17,21 +17,20 @@ struct sonya {
|
|||||||
case .isEmpty:
|
case .isEmpty:
|
||||||
print("sonya: sonyafile is empty")
|
print("sonya: sonyafile is empty")
|
||||||
exit(2)
|
exit(2)
|
||||||
case .syntaxError:
|
case .syntaxError(let errs):
|
||||||
print("sonya: syntax error in sonyafile")
|
for err in errs {
|
||||||
|
print("line '\(err)' is unrecognized")
|
||||||
|
}
|
||||||
exit(3)
|
exit(3)
|
||||||
case .noSuchRecipe:
|
case .noSuchRecipe:
|
||||||
print("sonya: no recipe named '\(recipe)'")
|
print("sonya: no recipe named '\(recipe)'")
|
||||||
exit(4)
|
exit(4)
|
||||||
case .noSuchDependency:
|
case .shellReturnedError(let code):
|
||||||
print("sonya: unknown dependency")
|
print("sonya: command failed with code \(code)")
|
||||||
exit(5)
|
exit(5)
|
||||||
case .shellReturnedError:
|
|
||||||
print("sonya: command failed")
|
|
||||||
exit(6)
|
|
||||||
case .noSuchVariable:
|
case .noSuchVariable:
|
||||||
print("sonya: undefined variable referenced in recipe '\(recipe)'")
|
print("sonya: undefined variable referenced in recipe '\(recipe)'")
|
||||||
exit(7)
|
exit(6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum runResult {
|
public enum runResult: Equatable {
|
||||||
case OK
|
case OK
|
||||||
case noSonyafile
|
case noSonyafile
|
||||||
case isEmpty
|
case isEmpty
|
||||||
case syntaxError
|
case syntaxError(errs: [String])
|
||||||
case noSuchRecipe
|
case noSuchRecipe
|
||||||
case noSuchDependency
|
case shellReturnedError(Int32)
|
||||||
case shellReturnedError
|
|
||||||
case noSuchVariable
|
case noSuchVariable
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,23 +23,28 @@ public func runRecipe(_ recipeName: String, args: [String] = []) -> runResult {
|
|||||||
let tokenized = Lexer().tokenize(sonyafile)
|
let tokenized = Lexer().tokenize(sonyafile)
|
||||||
guard !tokenized.isEmpty else { return .isEmpty }
|
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) {
|
||||||
guard let requestedRecipe = ast.rules[recipeName] else { return .noSuchRecipe }
|
case .success(let ast):
|
||||||
// check for dependencies
|
guard let requestedRecipe = ast.rules[recipeName] else { return .noSuchRecipe }
|
||||||
if !requestedRecipe.dependencies.isEmpty {
|
// check for dependencies
|
||||||
for dependency in requestedRecipe.dependencies {
|
if !requestedRecipe.dependencies.isEmpty {
|
||||||
let result = runRecipe(dependency)
|
for dependency in requestedRecipe.dependencies {
|
||||||
guard result == .OK else { return result }
|
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 }
|
||||||
|
|
||||||
|
let exitCode = shell(resolved)
|
||||||
|
guard exitCode == 0 else { return .shellReturnedError(exitCode) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// all good
|
||||||
|
return .OK
|
||||||
|
case .failure(let error):
|
||||||
|
return .syntaxError(errs: error.errorLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in requestedRecipe.commands {
|
|
||||||
guard let resolved = resolve(command, vars: ast.vars) else { return .noSuchVariable }
|
|
||||||
|
|
||||||
guard shell(resolved) == 0 else { return .shellReturnedError }
|
|
||||||
}
|
|
||||||
|
|
||||||
// all good
|
|
||||||
return .OK
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ enum Token : Equatable {
|
|||||||
case variable(name: String, value: String)
|
case variable(name: String, value: String)
|
||||||
case comment(String)
|
case comment(String)
|
||||||
case newline
|
case newline
|
||||||
|
case error(line: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Lexer {
|
struct Lexer {
|
||||||
@@ -75,7 +76,7 @@ struct Lexer {
|
|||||||
return [.command(String(match.1))]
|
return [.command(String(match.1))]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [] // unrecognized
|
return [.error(line: line)] // unrecognized
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
struct errorLines: Error {
|
||||||
|
var errorLines: [String]
|
||||||
|
|
||||||
|
init(_ errs: [String]) {
|
||||||
|
errorLines = errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct sonyaAST {
|
struct sonyaAST {
|
||||||
var vars: [String : String] = [:]
|
var vars: [String : String] = [:]
|
||||||
var rules: [String : Rule] = [:]
|
var rules: [String : Rule] = [:]
|
||||||
@@ -9,8 +17,9 @@ struct Rule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Parser {
|
struct Parser {
|
||||||
func parse(_ tokens: [Token]) -> sonyaAST? {
|
func parse(_ tokens: [Token]) -> Result<sonyaAST, errorLines> {
|
||||||
var ast = sonyaAST()
|
var ast = sonyaAST()
|
||||||
|
var errors: [String] = []
|
||||||
var currentRule: (name: String, rule: Rule)? = nil
|
var currentRule: (name: String, rule: Rule)? = nil
|
||||||
|
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
@@ -26,6 +35,8 @@ struct Parser {
|
|||||||
currentRule?.rule.dependencies.append(name)
|
currentRule?.rule.dependencies.append(name)
|
||||||
case .command(let cmd):
|
case .command(let cmd):
|
||||||
currentRule?.rule.commands.append(cmd)
|
currentRule?.rule.commands.append(cmd)
|
||||||
|
case .error(let line):
|
||||||
|
errors.append(line)
|
||||||
case .newline, .comment:
|
case .newline, .comment:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -33,7 +44,10 @@ struct Parser {
|
|||||||
|
|
||||||
if let rule = currentRule { ast.rules[rule.name] = rule.rule }
|
if let rule = currentRule { ast.rules[rule.name] = rule.rule }
|
||||||
currentRule = nil
|
currentRule = nil
|
||||||
return ast
|
if errors.isEmpty {
|
||||||
|
return .success(ast)
|
||||||
|
} else {
|
||||||
|
return .failure(errorLines(errors))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user