git.m455.casa

fa

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}