1use crate::ast::{Statement, StatementKind};
2use crate::error::{ParserError, Result};
3use crate::nim_ffi::{self, OwnedBuffer, ParseResultKind};
4
5pub fn parser_contract_version() -> String {
7 nim_ffi::parser_contract_version()
8}
9
10pub fn parse_sql(sql: &str) -> Result<Vec<Statement>> {
11 if sql.as_bytes().contains(&0) {
12 return Err(ParserError::UnexpectedToken {
13 line: 0,
14 column: 0,
15 expected: "valid SQL without interior NUL bytes".to_string(),
16 found: "interior NUL byte".to_string(),
17 });
18 }
19
20 let result = nim_ffi::parse_sql(sql);
21 match result.kind {
22 ParseResultKind::Ok => {
23 let buffer = OwnedBuffer::new(result.buffer_ptr, result.buffer_len);
24 if buffer.as_slice().is_empty() {
29 return Err(ParserError::UnexpectedToken {
30 line: 0,
31 column: 0,
32 expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
33 found: "empty payload from Nim parser (leaked exception at FFI boundary; \
34 see issue #40)"
35 .to_string(),
36 });
37 }
38 rmp_serde::from_slice::<Vec<Statement>>(buffer.as_slice()).map_err(|err| {
39 ParserError::UnexpectedToken {
40 line: 0,
41 column: 0,
42 expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
43 found: err.to_string(),
44 }
45 })
46 }
47 ParseResultKind::Error => {
48 let buffer = OwnedBuffer::new(result.error_ptr.cast(), result.error_len);
49 Err(parser_error_from_nim(
50 String::from_utf8_lossy(buffer.as_slice()).as_ref(),
51 ))
52 }
53 }
54}
55
56pub fn parse_expression_sql(sql: &str) -> Result<crate::ast::Expr> {
57 let wrapped = format!("SELECT {sql}");
58 let statements = parse_sql(&wrapped)?;
59 let Some(statement) = statements.into_iter().next() else {
60 return Err(empty_expression_error());
61 };
62 let StatementKind::Select(select) = statement.kind else {
63 return Err(empty_expression_error());
64 };
65 let Some(crate::ast::SelectItem::Expr { expr, .. }) = select.projection.into_iter().next()
66 else {
67 return Err(empty_expression_error());
68 };
69 Ok(expr)
70}
71
72fn empty_expression_error() -> ParserError {
73 ParserError::UnexpectedToken {
74 line: 0,
75 column: 0,
76 expected: "expression".to_string(),
77 found: "empty parser result".to_string(),
78 }
79}
80
81const INTERNAL_DEFECT_PREFIX: &str =
87 "internal parser defect (this is a parser bug, not invalid SQL): ";
88
89fn parser_error_from_nim(message: &str) -> ParserError {
90 if let Some(defect_message) = message.strip_prefix(INTERNAL_DEFECT_PREFIX) {
91 return ParserError::InternalParserDefect {
92 message: defect_message.to_string(),
93 };
94 }
95 let (line, column) = parse_nim_line_col(message).unwrap_or((0, 0));
96 ParserError::UnexpectedToken {
97 line,
98 column,
99 expected: "valid SQL".to_string(),
100 found: message.to_string(),
101 }
102}
103
104fn parse_nim_line_col(message: &str) -> Option<(u64, u64)> {
105 let after_line = message.strip_prefix("Parse error at line ")?;
106 let (line, rest) = after_line.split_once(", col ")?;
107 let (col, _) = rest.split_once(':')?;
108 Some((line.parse().ok()?, col.parse().ok()?))
109}