1#![forbid(unsafe_code)]
53#![deny(missing_docs)]
54
55use std::error::Error;
56use std::path::Path;
57
58use increparse::{Engine, Outcome, Pass, Schedule, Span};
59use increparse_lsp::{Document, FailedNode, Language};
60use lsp_types::{Diagnostic, DiagnosticSeverity, DocumentSymbol, SymbolKind};
61use mlua::{Function, Lua, Table, Value};
62
63#[derive(Debug, Clone)]
70pub struct LuaCtx(pub Value);
71
72impl PartialEq for LuaCtx {
73 fn eq(&self, other: &Self) -> bool {
74 deep_eq(&self.0, &other.0)
75 }
76}
77
78impl Eq for LuaCtx {}
79
80fn deep_eq(a: &Value, b: &Value) -> bool {
81 match (a, b) {
82 (Value::Nil, Value::Nil) => true,
83 (Value::Boolean(x), Value::Boolean(y)) => x == y,
84 (Value::Integer(x), Value::Integer(y)) => x == y,
85 (Value::Number(x), Value::Number(y)) => x == y,
86 (Value::Integer(x), Value::Number(y)) | (Value::Number(y), Value::Integer(x)) => {
87 (*x as f64) == *y
88 }
89 (Value::String(x), Value::String(y)) => x.as_bytes() == y.as_bytes(),
90 (Value::Table(x), Value::Table(y)) => {
91 let a: Vec<(Value, Value)> = x.pairs().filter_map(|p| p.ok()).collect();
92 let b: Vec<(Value, Value)> = y.pairs().filter_map(|p| p.ok()).collect();
93 if a.len() != b.len() {
94 return false;
95 }
96 a.iter()
97 .all(|(k, v)| b.iter().any(|(k2, v2)| deep_eq(k, k2) && deep_eq(v, v2)))
98 }
99 _ => false,
100 }
101}
102
103fn status_name(status: increparse::Status) -> &'static str {
104 match status {
105 increparse::Status::Unparsed => "unparsed",
106 increparse::Status::Expanded => "expanded",
107 increparse::Status::Done => "done",
108 increparse::Status::Failed => "failed",
109 }
110}
111
112#[derive(Clone)]
114pub(crate) struct LuaPass {
115 lua: Lua,
116 function: Function,
117 index: usize,
118}
119
120impl LuaPass {
121 fn try_parse(
122 &self,
123 source: &str,
124 span: Span,
125 ctx: &LuaCtx,
126 ) -> Result<Outcome<LuaCtx>, Box<dyn Error + Send + Sync>> {
127 let lua = &self.lua;
128 let span_table = lua.create_table()?;
129 span_table.set("start", span.start)?;
130 span_table.set("end", span.end)?;
131 span_table.set("rev", span.rev)?;
132
133 let result: Value = self.function.call((source, span_table, ctx.0.clone()))?;
134
135 match result {
136 Value::Nil => Ok(Outcome::Failed),
137 Value::String(s) if s.as_bytes() == b"done" => Ok(Outcome::Done),
138 Value::String(s) if s.as_bytes() == b"failed" => Ok(Outcome::Failed),
139 Value::Table(outcome) => {
140 let expand: Value = outcome.get("expand")?;
141 let Value::Table(array) = expand else {
142 eprintln!(
143 "increparse-lua: pass #{} returned a table without `expand`; treating as failed",
144 self.index
145 );
146 return Ok(Outcome::Failed);
147 };
148 let mut children = Vec::new();
149 for item in array.sequence_values::<Table>() {
150 let item = item?;
151 let start: usize = item.get("start")?;
152 let end: usize = item.get("end")?;
153 let child_ctx = LuaCtx(item.get::<Value>("ctx")?);
154 if start > end {
155 return Err(format!(
156 "pass #{} produced a child with start {start} > end {end}",
157 self.index
158 )
159 .into());
160 }
161 children.push((Span::new(start, end, span.rev), child_ctx));
162 }
163 Ok(Outcome::Expand(children))
164 }
165 other => {
166 eprintln!(
167 "increparse-lua: pass #{} returned {}; treating as failed",
168 self.index,
169 other.type_name()
170 );
171 Ok(Outcome::Failed)
172 }
173 }
174 }
175}
176
177impl Pass for LuaPass {
178 type Ctx = LuaCtx;
179
180 fn parse(&self, source: &str, span: Span, ctx: &LuaCtx) -> Outcome<LuaCtx> {
181 match self.try_parse(source, span, ctx) {
182 Ok(outcome) => outcome,
183 Err(err) => {
184 eprintln!("increparse-lua: pass #{} error: {err}", self.index);
185 Outcome::Failed
186 }
187 }
188 }
189
190 fn name(&self) -> &'static str {
191 "LuaPass"
192 }
193}
194
195pub struct LuaLanguage {
207 lua: Lua,
208 engine: Engine<LuaCtx>,
209 root_ctx: LuaCtx,
210 name: String,
211 diagnostic_fn: Option<Function>,
212 symbols_fn: Option<Function>,
213}
214
215impl LuaLanguage {
216 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Box<dyn Error + Send + Sync>> {
218 let lua = Lua::new();
219 let script = std::fs::read_to_string(path)?;
220 let config: Value = lua.load(script).eval()?;
221 Self::from_config(lua, config)
222 }
223
224 pub fn from_config(lua: Lua, config: Value) -> Result<Self, Box<dyn Error + Send + Sync>> {
226 let Value::Table(table) = &config else {
227 return Err("config must be a table".into());
228 };
229
230 let name: String = table.get("name")?;
231
232 let root_ctx = LuaCtx(table.get("root_ctx")?);
233
234 let passes_table: Table = table.get("passes")?;
235 let mut functions = Vec::new();
236 for f in passes_table.sequence_values::<Function>() {
237 functions.push(f?);
238 }
239 if functions.is_empty() {
240 return Err("config.passes must list at least one function".into());
241 }
242
243 let mut schedule = Schedule::new();
244 for (index, function) in functions.into_iter().enumerate() {
245 schedule.push(LuaPass {
246 lua: lua.clone(),
247 function,
248 index,
249 });
250 }
251 let engine = Engine::new(schedule);
252
253 let diagnostic_fn = match table.get::<Value>("diagnostic")? {
254 Value::Nil => None,
255 Value::Function(f) => Some(f),
256 other => {
257 return Err(format!(
258 "config.diagnostic must be a function, got {}",
259 other.type_name()
260 )
261 .into())
262 }
263 };
264 let symbols_fn = match table.get::<Value>("symbols")? {
265 Value::Nil => None,
266 Value::Function(f) => Some(f),
267 other => {
268 return Err(format!(
269 "config.symbols must be a function, got {}",
270 other.type_name()
271 )
272 .into())
273 }
274 };
275
276 Ok(Self {
277 lua,
278 engine,
279 root_ctx,
280 name,
281 diagnostic_fn,
282 symbols_fn,
283 })
284 }
285
286 pub fn name(&self) -> &str {
288 &self.name
289 }
290
291 pub fn lua(&self) -> &Lua {
293 &self.lua
294 }
295}
296
297impl Language<LuaCtx> for LuaLanguage {
298 fn supports_symbols(&self) -> bool {
299 self.symbols_fn.is_some()
300 }
301
302 fn engine(&self) -> &Engine<LuaCtx> {
303 &self.engine
304 }
305
306 fn root_ctx(&self) -> LuaCtx {
307 self.root_ctx.clone()
308 }
309
310 fn diagnostic(
311 &self,
312 doc: &Document<LuaCtx>,
313 node: FailedNode<'_, LuaCtx>,
314 ) -> Option<Diagnostic> {
315 let function = self.diagnostic_fn.as_ref()?;
316 let span = doc.session().tree().span(node.id);
317 let node_table = self.lua.create_table().ok()?;
318 node_table.set("ctx", node.ctx.0.clone()).ok()?;
319 node_table.set("status", status_name(node.status)).ok()?;
320 node_table.set("start", span.start).ok()?;
321 node_table.set("end", span.end).ok()?;
322
323 let result: Value = function.call((doc.text().to_string(), node_table)).ok()?;
324 let Value::Table(diag) = result else {
325 return None;
326 };
327 let message: String = diag.get("message").ok()?;
328 let severity = diag
329 .get::<i64>("severity")
330 .ok()
331 .and_then(|s| match s {
332 1 => Some(DiagnosticSeverity::ERROR),
333 2 => Some(DiagnosticSeverity::WARNING),
334 3 => Some(DiagnosticSeverity::INFORMATION),
335 4 => Some(DiagnosticSeverity::HINT),
336 _ => None,
337 })
338 .or(Some(DiagnosticSeverity::ERROR));
339
340 Some(Diagnostic {
341 message,
342 severity,
343 ..Diagnostic::default()
344 })
345 }
346
347 fn symbols(&self, doc: &Document<LuaCtx>) -> Vec<DocumentSymbol> {
348 let Some(function) = &self.symbols_fn else {
349 return Vec::new();
350 };
351 let Ok(nodes) = self.lua.create_table() else {
352 return Vec::new();
353 };
354 let tree = doc.session().tree();
355 for id in tree.nodes() {
356 let Ok(entry) = self.lua.create_table() else {
357 return Vec::new();
358 };
359 let span = tree.span(id);
360 if entry.set("start", span.start).is_err()
361 || entry.set("end", span.end).is_err()
362 || entry.set("status", status_name(tree.status(id))).is_err()
363 || entry.set("ctx", tree.ctx(id).0.clone()).is_err()
364 {
365 return Vec::new();
366 }
367 if nodes.push(entry).is_err() {
368 return Vec::new();
369 }
370 }
371
372 let result: Value = match function.call(nodes) {
373 Ok(v) => v,
374 Err(err) => {
375 eprintln!("increparse-lua: symbols error: {err}");
376 return Vec::new();
377 }
378 };
379 let Value::Table(array) = result else {
380 return Vec::new();
381 };
382
383 let mut symbols = Vec::new();
384 for item in array.sequence_values::<Table>() {
385 let Ok(item) = item else { break };
386 let Ok(name) = item.get::<String>("name") else {
387 continue;
388 };
389 let (Ok(start), Ok(end)) = (item.get::<usize>("start"), item.get::<usize>("end"))
390 else {
391 continue;
392 };
393 let detail: Option<String> = item.get("detail").unwrap_or(None);
394 let span = Span::new(start, end, doc.revision());
395 let range = doc.range(span);
396 symbols.push(DocumentSymbol {
397 name,
398 detail,
399 kind: SymbolKind::FUNCTION,
400 range,
401 selection_range: range,
402 children: None,
403 tags: None,
404 #[allow(deprecated)]
405 deprecated: None,
406 });
407 }
408 symbols
409 }
410}