You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lnav/src/third-party/prqlc-c/src/lib.rs

347 lines
11 KiB
Rust

#![cfg(not(target_family = "wasm"))]
extern crate libc;
use libc::{c_char, size_t};
use prqlc::{ErrorMessage, ErrorMessages};
use prqlc::Target;
use std::ffi::CStr;
use std::ffi::CString;
use std::panic;
use std::str::FromStr;
/// Compile a PRQL string into a SQL string.
///
/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without converting to JSON
/// between each of the functions.
///
/// See `Options` struct for available compilation options.
///
/// # Safety
///
/// This function assumes zero-terminated input strings.
/// Calling code is responsible for freeing memory allocated for `CompileResult`
/// by calling `result_destroy`.
#[no_mangle]
pub unsafe extern "C" fn compile(
prql_query: *const c_char,
options: *const Options,
) -> CompileResult {
let prql_query: String = c_str_to_string(prql_query);
let options = options.as_ref().map(convert_options).transpose();
let result = options
.and_then(|opts| {
panic::catch_unwind(|| {
Ok(prql_query.as_str())
.and_then(prqlc::prql_to_pl)
.and_then(prqlc::pl_to_rq)
.and_then(|rq| prqlc::rq_to_sql(rq, &opts.unwrap_or_default()))
}).map_err(|p| ErrorMessages {
inner: vec![ErrorMessage {
kind: prqlc::MessageKind::Error,
code: None,
reason: format!("internal error: {:#?}", p),
hints: vec![],
span: None,
display: None,
location: None,
}]
})?
})
.map_err(|e| e.composed(&prql_query.into()));
result_into_c_str(result)
}
/// Build PL AST from a PRQL string. PL in documented in the
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
///
/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
///
/// Returns 0 on success and a negative number -1 on failure.
///
/// # Safety
///
/// This function assumes zero-terminated input strings.
/// Calling code is responsible for freeing memory allocated for `CompileResult`
/// by calling `result_destroy`.
#[no_mangle]
pub unsafe extern "C" fn prql_to_pl(prql_query: *const c_char) -> CompileResult {
let prql_query: String = c_str_to_string(prql_query);
let result = Ok(prql_query.as_str())
.and_then(prqlc::prql_to_pl)
.and_then(|x| prqlc::json::from_pl(&x));
result_into_c_str(result)
}
/// Finds variable references, validates functions calls, determines frames and converts PL to RQ.
/// PL and RQ are documented in the
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ast).
///
/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out` buffer.
///
/// Returns 0 on success and a negative number -1 on failure.
///
/// # Safety
///
/// This function assumes zero-terminated input strings.
/// Calling code is responsible for freeing memory allocated for `CompileResult`
/// by calling `result_destroy`.
#[no_mangle]
pub unsafe extern "C" fn pl_to_rq(pl_json: *const c_char) -> CompileResult {
let pl_json: String = c_str_to_string(pl_json);
let result = Ok(pl_json.as_str())
.and_then(prqlc::json::to_pl)
.and_then(prqlc::pl_to_rq)
.and_then(|x| prqlc::json::from_rq(&x));
result_into_c_str(result)
}
/// Convert RQ AST into an SQL string. RQ is documented in the
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
///
/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
///
/// Returns 0 on success and a negative number -1 on failure.
///
/// # Safety
///
/// This function assumes zero-terminated input strings.
/// Calling code is responsible for freeing memory allocated for `CompileResult`
/// by calling `result_destroy`.
#[no_mangle]
pub unsafe extern "C" fn rq_to_sql(
rq_json: *const c_char,
options: *const Options,
) -> CompileResult {
let rq_json: String = c_str_to_string(rq_json);
let options = options.as_ref().map(convert_options).transpose();
let result = options.and_then(|options| {
Ok(rq_json.as_str())
.and_then(prqlc::json::to_rq)
.and_then(|x| prqlc::rq_to_sql(x, &options.unwrap_or_default()))
});
result_into_c_str(result)
}
/// Compilation options
#[repr(C)]
pub struct Options {
/// Pass generated SQL string trough a formatter that splits it
/// into multiple lines and prettifies indentation and spacing.
///
/// Defaults to true.
pub format: bool,
/// Target and dialect to compile to.
///
/// Defaults to `sql.any`, which uses `target` argument from the query header to determine
/// the SQL dialect.
pub target: *mut c_char,
/// Emits the compiler signature as a comment after generated SQL
///
/// Defaults to true.
pub signature_comment: bool,
}
/// Result of compilation.
#[repr(C)]
pub struct CompileResult {
pub output: *const libc::c_char,
pub messages: *const Message,
pub messages_len: size_t,
}
/// Compile message kind. Currently only Error is implemented.
#[repr(C)]
pub enum MessageKind {
Error,
Warning,
Lint,
}
/// Compile result message.
///
/// Calling code is responsible for freeing all memory allocated
/// for fields as well as strings.
// Make sure to keep in sync with prqlc::ErrorMessage
#[repr(C)]
pub struct Message {
/// Message kind. Currently only Error is implemented.
pub kind: MessageKind,
/// Machine-readable identifier of the error
pub code: *const *const libc::c_char,
/// Plain text of the error
pub reason: *const libc::c_char,
/// A list of suggestions of how to fix the error
pub hint: *const *const libc::c_char,
/// Character offset of error origin within a source file
pub span: *const Span,
/// Annotated code, containing cause and hints.
pub display: *const *const libc::c_char,
/// Line and column number of error origin within a source file
pub location: *const SourceLocation,
}
/// Identifier of a location in source.
/// Contains offsets in terms of chars.
// Make sure to keep in sync with prqlc::Span
#[repr(C)]
pub struct Span {
pub start: size_t,
pub end: size_t,
}
/// Location within a source file.
// Make sure to keep in sync with prqlc::SourceLocation
#[repr(C)]
pub struct SourceLocation {
pub start_line: size_t,
pub start_col: size_t,
pub end_line: size_t,
pub end_col: size_t,
}
/// Destroy a `CompileResult` once you are done with it.
///
/// # Safety
///
/// This function expects to be called exactly once after the call of any the functions
/// that return `CompileResult`. No fields should be freed manually.
#[no_mangle]
pub unsafe extern "C" fn result_destroy(res: CompileResult) {
// This is required because we are allocating memory for
// strings, vectors and options.
// For strings and vectors this is required, but options may be
// able to live entirely within the struct, instead of the heap.
for i in 0..res.messages_len {
let e = &*res.messages.add(i);
if !e.code.is_null() {
drop(CString::from_raw(*e.code as *mut libc::c_char));
drop(Box::from_raw(e.code as *mut *const libc::c_char));
}
drop(CString::from_raw(e.reason as *mut libc::c_char));
if !e.hint.is_null() {
drop(CString::from_raw(*e.hint as *mut libc::c_char));
drop(Box::from_raw(e.hint as *mut *const libc::c_char));
}
if !e.span.is_null() {
drop(Box::from_raw(e.span as *mut Span));
}
if !e.display.is_null() {
drop(CString::from_raw(*e.display as *mut libc::c_char));
drop(Box::from_raw(e.display as *mut *const libc::c_char));
}
if !e.location.is_null() {
drop(Box::from_raw(e.location as *mut SourceLocation));
}
}
drop(Vec::from_raw_parts(
res.messages as *mut i8,
res.messages_len,
res.messages_len,
));
drop(CString::from_raw(res.output as *mut libc::c_char));
}
unsafe fn result_into_c_str(result: Result<String, ErrorMessages>) -> CompileResult {
match result {
Ok(output) => CompileResult {
output: convert_string(output),
messages: ::std::ptr::null_mut(),
messages_len: 0,
},
Err(err) => {
let mut errors = Vec::with_capacity(err.inner.len());
errors.extend(err.inner.into_iter().map(|e| Message {
kind: MessageKind::Error,
code: option_to_ptr(e.code.map(convert_string)),
reason: convert_string(e.reason),
hint: option_to_ptr(if e.hints.is_empty() {
None
} else {
Some(convert_string(e.hints.join("\n")))
}),
span: option_to_ptr(e.span.map(convert_span)),
display: option_to_ptr(e.display.map(convert_string)),
location: option_to_ptr(e.location.map(convert_source_location)),
}));
CompileResult {
output: CString::default().into_raw(),
messages_len: errors.len(),
messages: errors.leak().as_ptr(),
}
}
}
}
/// Allocates the value on the heap and returns a pointer to it.
/// If the input is None, it returns null pointer.
fn option_to_ptr<T>(o: Option<T>) -> *const T {
match o {
Some(x) => {
let b = Box::new(x);
Box::into_raw(b)
}
None => ::std::ptr::null(),
}
}
fn convert_string(x: String) -> *const libc::c_char {
CString::new(x).unwrap_or_default().into_raw()
}
fn convert_span(x: prqlc::Span) -> Span {
Span {
start: x.start,
end: x.end,
}
}
fn convert_source_location(x: prqlc::SourceLocation) -> SourceLocation {
SourceLocation {
start_line: x.start.0,
start_col: x.start.1,
end_line: x.end.0,
end_col: x.end.1,
}
}
unsafe fn c_str_to_string(c_str: *const c_char) -> String {
// inefficient, but simple
CStr::from_ptr(c_str).to_string_lossy().into_owned()
}
fn convert_options(o: &Options) -> Result<prqlc::Options, prqlc::ErrorMessages> {
let target = if !o.target.is_null() {
Some(unsafe { c_str_to_string(o.target) })
} else {
None
};
let target = target
.as_deref()
.filter(|x| !x.is_empty())
.unwrap_or("sql.any");
let target = Target::from_str(target).map_err(prqlc::ErrorMessages::from)?;
Ok(prqlc::Options {
format: o.format,
target,
signature_comment: o.signature_comment,
// TODO: add support for this
color: false,
})
}