Skip to content

Commit

Permalink
feat: report errors during evaluation
Browse files Browse the repository at this point in the history
- Errors thrown in resolvables will now be wrapped and associated with the relevant AST node
- The error is then formatted in a code frame that highlights the relevant region of the nanoweave code where the error occurred
- Object/pair parsers now produce AST
  • Loading branch information
NoxHarmonium committed Aug 20, 2023
1 parent 6d10347 commit 73c9556
Show file tree
Hide file tree
Showing 35 changed files with 630 additions and 277 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,23 @@ It should output an image file like the following:

![AST Image Example](doc/ast.png)

## REPL

Start the REPL with `lein repl`

This will drop you into the namespace `repl-env` which has
a function called `refresh` in scope that allows you to
safely reload the source files if they change.

```
(ns repl-env)
(reload)
```

See also:

https://stackoverflow.com/a/25979645/1153203

## License

Copyright 2018 Sean Dawson
Expand Down
8 changes: 8 additions & 0 deletions profiles/repl/repl_env.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
;; TODO DON'T EVALUATE THIS IN EASTWOOD
;; SEEMS LIKE IT MIGHT CAUSE ISSUES
;; EXCLUDE FROM LINTINGH????

(ns repl-env
(:require [clojure.tools.namespace.repl :refer [refresh]]))

(refresh)
9 changes: 7 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
:target-path "target/%s"
:global-vars {*warn-on-reflection* true}
:jvm-opts ["-Djava.awt.headless=true"]
:plugins [[lein-codox "0.10.8"]]
:plugins []
:profiles {:uberjar {:aot :all}
:repl {:source-paths ["src" "profiles"]
:repl-options {:init-ns repl.repl-env}}
:dev {:resource-paths ["test/resources"]
:plugins [[rasom/lein-githooks "0.1.5"]
[jonase/eastwood "1.4.0"]
[lein-cljfmt "0.9.2"]
[com.github.clj-kondo/lein-clj-kondo "0.2.5"]]
[com.github.clj-kondo/lein-clj-kondo "0.2.5"]
[lein-codox "0.10.8"]]
:dependencies [[org.clojure/tools.namespace "1.3.0"]
[diff-eq "0.2.5"]]
:githooks {:pre-push ["lein check" "lein test"]
:pre-commit ["lein eastwood" "lein clj-kondo" "lein cljfmt fix"]}}}
:eastwood {:config-files ["lint_config.clj"]
Expand Down
6 changes: 5 additions & 1 deletion src/nanoweave/ast/base.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
The tokens are parsed to first class records rather than just clojure functions
so that in the future they can be extended with more features such as
better error handling and type validation.", :author "Sean Dawson"}
nanoweave.ast.base)
nanoweave.ast.base
(:require [schema.core :as s]))

(defprotocol Resolvable
"Describes an AST node that can be resolved with an input.
Used to transform AST trees into a final value."
(resolve-value [this input] "Takes an input and resolves it to a final value."))

(s/defrecord AstPos [line :- s/Int col :- s/Int src :- s/Str])
(s/defrecord AstSpan [start :- AstPos end :- AstPos])
13 changes: 7 additions & 6 deletions src/nanoweave/ast/binary_arithmetic.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
:author "Sean Dawson"}
nanoweave.ast.binary-arithmetic
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord AddOp [left :- Resolvable right :- Resolvable])
(s/defrecord SubOp [left :- Resolvable right :- Resolvable])
(s/defrecord MultOp [left :- Resolvable right :- Resolvable])
(s/defrecord DivOp [left :- Resolvable right :- Resolvable])
(s/defrecord ModOp [left :- Resolvable right :- Resolvable])
(s/defrecord AddOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord SubOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord MultOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord DivOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord ModOp [span :- AstSpan left :- Resolvable right :- Resolvable])
15 changes: 8 additions & 7 deletions src/nanoweave/ast/binary_functions.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
:author "Sean Dawson"}
nanoweave.ast.binary-functions
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord MapOp [left :- Resolvable right :- Resolvable])
(s/defrecord FilterOp [left :- Resolvable right :- Resolvable])
(s/defrecord ReduceOp [left :- Resolvable right :- Resolvable])
(s/defrecord MapOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord FilterOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord ReduceOp [span :- AstSpan left :- Resolvable right :- Resolvable])

(s/defrecord RegexMatchOp [left :- Resolvable right :- Resolvable])
(s/defrecord RegexFindOp [left :- Resolvable right :- Resolvable])
(s/defrecord RegexSplitOp [left :- Resolvable right :- Resolvable])
(s/defrecord RegexMatchOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord RegexFindOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord RegexSplitOp [span :- AstSpan left :- Resolvable right :- Resolvable])
21 changes: 11 additions & 10 deletions src/nanoweave/ast/binary_logic.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
:author "Sean Dawson"}
nanoweave.ast.binary-logic
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord EqOp [left :- Resolvable right :- Resolvable])
(s/defrecord NotEqOp [left :- Resolvable right :- Resolvable])
(s/defrecord LessThanOp [left :- Resolvable right :- Resolvable])
(s/defrecord LessThanEqOp [left :- Resolvable right :- Resolvable])
(s/defrecord GrThanOp [left :- Resolvable right :- Resolvable])
(s/defrecord GrThanEqOp [left :- Resolvable right :- Resolvable])
(s/defrecord EqOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord NotEqOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord LessThanOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord LessThanEqOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord GrThanOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord GrThanEqOp [span :- AstSpan left :- Resolvable right :- Resolvable])

(s/defrecord AndOp [left :- Resolvable right :- Resolvable])
(s/defrecord OrOp [left :- Resolvable right :- Resolvable])
(s/defrecord XorOp [left :- Resolvable right :- Resolvable])
(s/defrecord AndOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord OrOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord XorOp [span :- AstSpan left :- Resolvable right :- Resolvable])
15 changes: 8 additions & 7 deletions src/nanoweave/ast/binary_other.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
:author "Sean Dawson"}
nanoweave.ast.binary-other
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord DotOp [left :- Resolvable right :- Resolvable])
(s/defrecord ConcatOp [left :- Resolvable right :- Resolvable])
(s/defrecord OpenRangeOp [left :- Resolvable right :- Resolvable])
(s/defrecord ClosedRangeOp [left :- Resolvable right :- Resolvable])
(s/defrecord IsOp [left :- Resolvable right :- Resolvable])
(s/defrecord AsOp [left :- Resolvable right :- Resolvable])
(s/defrecord DotOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord ConcatOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord OpenRangeOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord ClosedRangeOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord IsOp [span :- AstSpan left :- Resolvable right :- Resolvable])
(s/defrecord AsOp [span :- AstSpan left :- Resolvable right :- Resolvable])
11 changes: 6 additions & 5 deletions src/nanoweave/ast/lambda.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
:author "Sean Dawson"}
nanoweave.ast.lambda
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord Lambda [param-list :- [s/Str] body :- Resolvable])
(s/defrecord NoArgsLambda [body :- Resolvable])
(s/defrecord FunCall [target :- Resolvable args :- [Resolvable]])
(s/defrecord ArgList [arguments :- [Resolvable]])
(s/defrecord Lambda [span :- AstSpan param-list :- [s/Str] body :- Resolvable])
(s/defrecord NoArgsLambda [span :- AstSpan body :- Resolvable])
(s/defrecord FunCall [span :- AstSpan target :- Resolvable args :- [Resolvable]])
(s/defrecord ArgList [span :- AstSpan arguments :- [Resolvable]])
19 changes: 11 additions & 8 deletions src/nanoweave/ast/literals.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
:author "Sean Dawson"}
nanoweave.ast.literals
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord IdentiferLit [value :- s/Str static-prefix :- s/Bool])
(s/defrecord StringLit [value :- s/Str])
(s/defrecord FloatLit [value :- s/Num])
(s/defrecord BoolLit [value :- s/Bool])
(s/defrecord NilLit [])
(s/defrecord ArrayLit [value :- [Resolvable]])
(s/defrecord TypeLit [value :- (s/enum "Number" "String" "Boolean" "Nil" "Array")])
(s/defrecord IdentiferLit [span :- AstSpan value :- s/Str static-prefix :- s/Bool])
(s/defrecord StringLit [span :- AstSpan value :- s/Str])
(s/defrecord FloatLit [span :- AstSpan value :- s/Num])
(s/defrecord BoolLit [span :- AstSpan value :- s/Bool])
(s/defrecord NilLit [span :- AstSpan])
(s/defrecord ArrayLit [span :- AstSpan value :- [Resolvable]])
(s/defrecord TypeLit [span :- AstSpan value :- (s/enum "Number" "String" "Boolean" "Nil" "Array")])
(s/defrecord PairLit [span :- AstSpan key :- Resolvable value :- Resolvable])
(s/defrecord ObjectLit [span :- AstSpan pairs :- [Resolvable]])
21 changes: 11 additions & 10 deletions src/nanoweave/ast/pattern_matching.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
:author "Sean Dawson"}
nanoweave.ast.pattern-matching
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord ListPatternMatchOp [targets :- [Resolvable]])
(s/defrecord MapPatternMatchOp [targets :- [Resolvable]])
(s/defrecord RegexMatchOp [pattern :- Resolvable])
(s/defrecord VariableMatchOp [target :- Resolvable])
(s/defrecord LiteralMatchOp [target :- Resolvable])
(s/defrecord ListPatternMatchOp [span :- AstSpan targets :- [Resolvable]])
(s/defrecord MapPatternMatchOp [span :- AstSpan targets :- [Resolvable]])
(s/defrecord RegexMatchOp [span :- AstSpan pattern :- Resolvable])
(s/defrecord VariableMatchOp [span :- AstSpan target :- Resolvable])
(s/defrecord LiteralMatchOp [span :- AstSpan target :- Resolvable])

(s/defrecord KeyMatchOp [target :- Resolvable])
(s/defrecord KeyValueMatchOp [key :- Resolvable value :- Resolvable])
(s/defrecord Match [clauses :- [Resolvable] target :- Resolvable])
(s/defrecord MatchClause [match :- Resolvable body :- Resolvable])
(s/defrecord KeyMatchOp [span :- AstSpan target :- Resolvable])
(s/defrecord KeyValueMatchOp [span :- AstSpan key :- Resolvable value :- Resolvable])
(s/defrecord Match [span :- AstSpan clauses :- [Resolvable] target :- Resolvable])
(s/defrecord MatchClause [span :- AstSpan match :- Resolvable body :- Resolvable])
15 changes: 8 additions & 7 deletions src/nanoweave/ast/scope.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
:author "Sean Dawson"}
nanoweave.ast.scope
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord Binding [match :- Resolvable value :- Resolvable body :- Resolvable])
(s/defrecord Expression [body :- Resolvable])
(s/defrecord Indexing [target :- Resolvable key :- Resolvable])
(s/defrecord ImportOp [class-name :- Resolvable])
(s/defrecord When [clauses :- [Resolvable]])
(s/defrecord WhenClause [condition :- Resolvable body :- Resolvable])
(s/defrecord Binding [span :- AstSpan match :- Resolvable value :- Resolvable body :- Resolvable])
(s/defrecord Expression [span :- AstSpan body :- Resolvable])
(s/defrecord Indexing [span :- AstSpan target :- Resolvable key :- Resolvable])
(s/defrecord ImportOp [span :- AstSpan class-name :- Resolvable])
(s/defrecord When [span :- AstSpan clauses :- [Resolvable]])
(s/defrecord WhenClause [span :- AstSpan condition :- Resolvable body :- Resolvable])
7 changes: 4 additions & 3 deletions src/nanoweave/ast/text.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
:author "Sean Dawson"}
nanoweave.ast.text
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord InterpolatedString [body :- [Resolvable]])
(s/defrecord Regex [regex :- [java.util.regex.Pattern]])
(s/defrecord InterpolatedString [span :- AstSpan body :- [Resolvable]])
(s/defrecord Regex [span :- AstSpan regex :- [java.util.regex.Pattern]])
9 changes: 5 additions & 4 deletions src/nanoweave/ast/unary.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
:author "Sean Dawson"}
nanoweave.ast.unary
(:require [schema.core :as s]
[nanoweave.ast.base :refer [Resolvable]]))
[nanoweave.ast.base :refer [Resolvable]])
(:import [nanoweave.ast.base AstSpan]))

(s/defrecord NotOp [value :- Resolvable])
(s/defrecord NegOp [value :- Resolvable])
(s/defrecord TypeOfOp [value :- Resolvable])
(s/defrecord NotOp [span :- AstSpan value :- Resolvable])
(s/defrecord NegOp [span :- AstSpan value :- Resolvable])
(s/defrecord TypeOfOp [span :- AstSpan value :- Resolvable])
4 changes: 2 additions & 2 deletions src/nanoweave/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
should exit (with a error message, and optional ok status), or a map
indicating the action the program should take and the options provided."
[args]
(let [{:keys [options arguments errors summary]} (parse-opts args
cli-options)]
(let [{:keys [options arguments errors summary]}
(parse-opts args cli-options)]
(cond
(:help options) ; help => exit OK with usage summary
{:exit-message (usage summary), :ok? true}
Expand Down
84 changes: 61 additions & 23 deletions src/nanoweave/parsers/base.clj
Original file line number Diff line number Diff line change
@@ -1,30 +1,68 @@
(ns ^{:doc "Parses the basic structure of a transform definition.", :author "Sean Dawson"}
nanoweave.parsers.base
(:require [blancas.kern.core :refer [bind <|> <?> fwd return]]
[blancas.kern.lexer.java-style :refer
[colon brackets braces comma-sep string-lit identifier]]
[nanoweave.ast.literals :refer [->ArrayLit]]
[nanoweave.utils :refer [declare-extern]]))
(:require [blancas.kern.core :refer [<|> bind put-state return]]
[nanoweave.ast.base :refer [->AstPos ->AstSpan]]
[nanoweave.utils :refer [declare-extern]]
[schema.core :refer [validate]])
(:import [nanoweave.ast.base AstSpan]))

; Forward declarations

(declare-extern nanoweave.parsers.expr/expr)

; JSON Elements

(def pair
"Parses the rule: pair := String ':' expr"
(<?> (bind [key (<|> string-lit identifier)
_ colon
value (fwd nanoweave.parsers.expr/expr)] (return [key value]))
"pair"))
(def array
"Parses the rule: array := '[' (expr (',' expr)*)* ']'"
(<?> (brackets (bind [members (comma-sep (fwd nanoweave.parsers.expr/expr))]
(return (->ArrayLit members))))
"array"))
(def object
"Parses the rule: object := '{' (pair (',' pair)*)* '}'"
(<?> (braces (bind [members (comma-sep pair)]
(return (apply hash-map (reduce concat [] members)))))
"object"))
;; Utility

(defn <s>
"Pushes the current position to the user state so it can be used to calculate the AST span"
[p]
(fn [current-state]
(let [current-pos (:pos current-state)
current-user-state (:user current-state)
prev-stack (:prev-position current-user-state)
orig-stack-len (count prev-stack)
pushed-stack (conj prev-stack current-pos)
final-state ((put-state {:prev-position pushed-stack}) current-state)
output (p final-state)
after-stack-length (count (:prev-position (:user current-state)))]
(assert (= orig-stack-len after-stack-length) "Mismatched calls to <s> and pop-span. There should be one call to pop-span for each call to <s>")
output)))

(defn pop-span
"Wraps an AST constructor so that the AST span is automatically applied as an argument after parsing"
[current-state]
(let [curr-pos (:pos current-state)
current-user-state (:user current-state)
prev-stack (:prev-position current-user-state)
prev-pos (peek prev-stack)
span (->AstSpan
(->AstPos (:line prev-pos) (:col prev-pos) (:src prev-pos))
(->AstPos (:line curr-pos) (dec (:col curr-pos)) (:src curr-pos)))
popped-stack (pop prev-stack)
new-state ((put-state {:prev-position popped-stack}) current-state)
constructor-fn (fn [rec & args]
(fn [& more-args]
(apply rec span (concat args more-args))))]
((return constructor-fn) new-state)))

; TODO Move this

(defn merge-span
"Takes two AST objects and creates a new AstSpan object that spans the two objects.
Usually used to create span for binary operators so the span covers both sides of the operator."
[a b]
(let [span-a (:span a) span-b (:span b)]
(validate AstSpan span-a)
(validate AstSpan span-b)
(->AstSpan (:start span-a) (:end span-b))))

(defn chainl1*
"Specialisation of Kern's chainl1 function that ensures that binary operator AST
have spans the cover both sides of the operator.
Parses p; as long as there is a binary operator op, reads the op and
another instance of p, then applies the operator on both values.
The operator associates to the left."
[p op]
(letfn [(rest [a] (<|> (bind [f op b p] (rest (f (merge-span a b) a b)))
(return a)))]
(bind [a p] (rest a))))
Loading

0 comments on commit 73c9556

Please sign in to comment.