clone url: git://git.m455.casa/fa
esperbuild/espersrc/fennel-0.7.0/src/fennel/friend.fnl
1 | ;; This module contains functions that handle errors during parsing and |
2 | ;; compilation and attempt to enrich them by suggesting fixes. |
3 | ;; It can be disabled to fall back to the regular terse errors. |
4 |
|
5 | (fn ast-source [ast] |
6 | (let [m (getmetatable ast)] |
7 | (if (and m m.line m) m ast))) |
8 |
|
9 | (local suggestions |
10 | {"unexpected multi symbol (.*)" |
11 | ["removing periods or colons from %s"] |
12 |
|
13 | "use of global (.*) is aliased by a local" |
14 | ["renaming local %s" |
15 | "refer to the global using _G.%s instead of directly"] |
16 |
|
17 | "local (.*) was overshadowed by a special form or macro" |
18 | ["renaming local %s"] |
19 |
|
20 | "global (.*) conflicts with local" |
21 | ["renaming local %s"] |
22 |
|
23 | "expected var (.*)" |
24 | ["declaring %s using var instead of let/local" |
25 | "introducing a new local instead of changing the value of %s"] |
26 |
|
27 | "expected macros to be table" |
28 | ["ensuring your macro definitions return a table"] |
29 |
|
30 | "expected each macro to be function" |
31 | ["ensuring that the value for each key in your macros table contains a function" |
32 | "avoid defining nested macro tables"] |
33 |
|
34 | "macro not found in macro module" |
35 | ["checking the keys of the imported macro module's returned table"] |
36 |
|
37 | "macro tried to bind (.*) without gensym" |
38 | ["changing to %s# when introducing identifiers inside macros"] |
39 |
|
40 | "unknown global in strict mode: (.*)" |
41 | ["looking to see if there's a typo" |
42 | "using the _G table instead, eg. _G.%s if you really want a global" |
43 | "moving this code to somewhere that %s is in scope" |
44 | "binding %s as a local in the scope of this code"] |
45 |
|
46 | "expected a function.* to call" |
47 | ["removing the empty parentheses" |
48 | "using square brackets if you want an empty table"] |
49 |
|
50 | "cannot call literal value" |
51 | ["checking for typos" |
52 | "checking for a missing function name"] |
53 |
|
54 | "unexpected vararg" |
55 | ["putting \"...\" at the end of the fn parameters if the vararg was intended"] |
56 |
|
57 | "multisym method calls may only be in call position" |
58 | ["using a period instead of a colon to reference a table's fields" |
59 | "putting parens around this"] |
60 |
|
61 | "unused local (.*)" |
62 | ["fixing a typo so %s is used" |
63 | "renaming the local to _%s"] |
64 |
|
65 | "expected parameters" |
66 | ["adding function parameters as a list of identifiers in brackets"] |
67 |
|
68 | "unable to bind (.*)" |
69 | ["replacing the %s with an identifier"] |
70 |
|
71 | "expected rest argument before last parameter" |
72 | ["moving & to right before the final identifier when destructuring"] |
73 |
|
74 | "expected vararg as last parameter" |
75 | ["moving the \"...\" to the end of the parameter list"] |
76 |
|
77 | "expected symbol for function parameter: (.*)" |
78 | ["changing %s to an identifier instead of a literal value"] |
79 |
|
80 | "could not compile value of type " |
81 | ["debugging the macro you're calling not to return a coroutine or userdata"] |
82 |
|
83 | "expected local" |
84 | ["looking for a typo" |
85 | "looking for a local which is used out of its scope"] |
86 |
|
87 | "expected body expression" |
88 | ["putting some code in the body of this form after the bindings"] |
89 |
|
90 | "expected binding table" |
91 | ["placing a table here in square brackets containing identifiers to bind"] |
92 |
|
93 | "expected even number of name/value bindings" |
94 | ["finding where the identifier or value is missing"] |
95 |
|
96 | "may only be used at compile time" |
97 | ["moving this to inside a macro if you need to manipulate symbols/lists" |
98 | "using square brackets instead of parens to construct a table"] |
99 |
|
100 | "unexpected closing delimiter (.)" |
101 | ["deleting %s" |
102 | "adding matching opening delimiter earlier"] |
103 |
|
104 | "mismatched closing delimiter (.), expected (.)" |
105 | ["replacing %s with %s" |
106 | "deleting %s" |
107 | "adding matching opening delimiter earlier"] |
108 |
|
109 | "expected even number of values in table literal" |
110 | ["removing a key" |
111 | "adding a value"] |
112 |
|
113 | "expected whitespace before opening delimiter" |
114 | ["adding whitespace"] |
115 |
|
116 | "illegal character: (.)" |
117 | ["deleting or replacing %s" |
118 | "avoiding reserved characters like \", \\, ', ~, ;, @, `, and comma"] |
119 |
|
120 | "could not read number (.*)" |
121 | ["removing the non-digit character" |
122 | "beginning the identifier with a non-digit if it is not meant to be a number"] |
123 |
|
124 | "can't start multisym segment with a digit" |
125 | ["removing the digit" |
126 | "adding a non-digit before the digit"] |
127 |
|
128 | "malformed multisym" |
129 | ["ensuring each period or colon is not followed by another period or colon"] |
130 |
|
131 | "method must be last component" |
132 | ["using a period instead of a colon for field access" |
133 | "removing segments after the colon" |
134 | "making the method call, then looking up the field on the result"] |
135 |
|
136 | "$ and $... in hashfn are mutually exclusive" |
137 | ["modifying the hashfn so it only contains $... or $, $1, $2, $3, etc"]}) |
138 |
|
139 | (local unpack (or _G.unpack table.unpack)) |
140 |
|
141 | (fn suggest [msg] |
142 | (var suggestion nil) |
143 | (each [pat sug (pairs suggestions)] |
144 | (let [matches [(msg:match pat)]] |
145 | (when (< 0 (# matches)) |
146 | (set suggestion (if (= :table (type sug)) |
147 | (let [out []] |
148 | (each [_ s (ipairs sug)] |
149 | (table.insert out (s:format (unpack matches)))) |
150 | out) |
151 | (sug matches)))))) |
152 | suggestion) |
153 |
|
154 | (fn read-line-from-file [filename line] |
155 | (var bytes 0) |
156 | (let [f (assert (io.open filename)) |
157 | _ (for [_ 1 (- line 1)] |
158 | (set bytes (+ bytes 1 (# (f:read))))) |
159 | codeline (f:read)] |
160 | (f:close) |
161 | (values codeline bytes))) |
162 |
|
163 | (fn read-line-from-source [source line] |
164 | (var (lines bytes codeline) (values 0 0)) |
165 | (each [this-line newline (string.gmatch (.. source "\n") "(.-)(\r?\n)")] |
166 | (set lines (+ lines 1)) |
167 | (when (= lines line) |
168 | (set codeline this-line) |
169 | (lua :break)) |
170 | (set bytes (+ bytes (# newline) (# this-line)))) |
171 | (values codeline bytes)) |
172 |
|
173 | (fn read-line [filename line source] |
174 | (if source |
175 | (read-line-from-source source line) |
176 | (read-line-from-file filename line))) |
177 |
|
178 | (fn friendly-msg [msg {: filename : line : bytestart : byteend} source] |
179 | (let [(ok codeline bol) (pcall read-line filename line source) |
180 | suggestions (suggest msg) |
181 | out [msg ""]] |
182 | ;; don't assume the file can be read as-is |
183 | ;; (when (not ok) (print :err codeline)) |
184 | (when (and ok codeline) |
185 | (table.insert out codeline)) |
186 | (when (and ok codeline bytestart byteend) |
187 | (table.insert out (.. (string.rep " " (- bytestart bol 1)) "^" |
188 | (string.rep "^" (math.min (- byteend bytestart) |
189 | (- (+ bol (# codeline)) |
190 | bytestart)))))) |
191 | (when (and ok codeline bytestart (not byteend)) |
192 | (table.insert out (.. (string.rep "-" (- bytestart bol 1)) "^")) |
193 | (table.insert out "")) |
194 | (when suggestions |
195 | (each [_ suggestion (ipairs suggestions)] |
196 | (table.insert out (: "* Try %s." :format suggestion)))) |
197 | (table.concat out "\n"))) |
198 |
|
199 | (fn assert-compile [condition msg ast source] |
200 | "A drop-in replacement for the internal assert-compile with friendly messages." |
201 | (when (not condition) |
202 | (let [{: filename : line} (ast-source ast)] |
203 | (error (friendly-msg (: "Compile error in %s:%s\n %s" :format |
204 | ;; still need fallbacks because backtick erases |
205 | ;; source data, and vararg has no source data |
206 | (or filename :unknown) (or line :?) msg) |
207 | (ast-source ast) source) 0))) |
208 | condition) |
209 |
|
210 | (fn parse-error [msg filename line bytestart source] |
211 | "A drop-in replacement for the internal parse-error with friendly messages." |
212 | (error (friendly-msg (: "Parse error in %s:%s\n %s" :format filename line msg) |
213 | {: filename : line : bytestart} source) 0)) |
214 |
|
215 | {: assert-compile : parse-error} |