use crate::ast::Helper;
use crate::ast::{Docs, Warning};
use crate::ctx::EventContext;
use crate::errors::{CompilerError, Error, Result};
use crate::highlighter::{Dumb as DumbHighlighter, Highlighter};
pub use crate::interpreter::AggrType;
use crate::interpreter::Cont;
use crate::lexer::{self};
use crate::parser::g as grammar;
use crate::path::ModulePath;
use crate::pos::Range;
use crate::registry::{Aggr as AggrRegistry, Registry};
use serde::Serialize;
use simd_json::borrowed::Value;
use std::io::{self, Write};
#[derive(Debug, Serialize, PartialEq)]
pub enum Return<'event> {
Emit {
value: Value<'event>,
port: Option<String>,
},
Drop,
EmitEvent {
port: Option<String>,
},
}
impl<'run, 'event> From<Cont<'run, 'event>> for Return<'event>
where
'event: 'run,
{
fn from(v: Cont<'run, 'event>) -> Self {
match v {
Cont::Cont(value) => Return::Emit {
value: value.into_owned(),
port: None,
},
Cont::Emit(value, port) => Return::Emit { value, port },
Cont::EmitEvent(port) => Return::EmitEvent { port },
Cont::Drop => Return::Drop,
}
}
}
#[derive(Debug)]
pub struct Script {
pub(crate) script: rentals::Script,
source: String,
warnings: Vec<Warning>,
}
impl Script {
pub fn warnings(&self) -> &Vec<Warning> {
&self.warnings
}
}
rental! {
mod rentals {
use crate::ast;
use std::borrow::Cow;
use serde::Serialize;
use std::sync::Arc;
use std::marker::Send;
#[rental_mut(covariant,debug)]
pub(crate) struct Script{
script: Box<String>,
parsed: ast::Script<'script>
}
}
}
impl<'run, 'event, 'script> Script
where
'script: 'event,
'event: 'run,
{
pub fn parse(
module_path: &ModulePath,
file_name: &str,
script: String,
reg: &Registry,
) -> std::result::Result<Self, CompilerError> {
let mut include_stack = lexer::IncludeStack::default();
let r = |include_stack: &mut lexer::IncludeStack| -> Result<Self> {
let mut warnings = vec![];
let rented_script =
rentals::Script::try_new(Box::new(script.clone()), |script: &mut String| {
let cu = include_stack.push(file_name)?;
let lexemes: Vec<_> = lexer::Preprocessor::preprocess(
module_path,
file_name,
script,
cu,
include_stack,
)?;
let filtered_tokens = lexemes
.into_iter()
.filter_map(Result::ok)
.filter(|t| !t.value.is_ignorable());
let script_raw = grammar::ScriptParser::new().parse(filtered_tokens)?;
let fake_aggr_reg = AggrRegistry::default();
let mut helper = Helper::new(®, &fake_aggr_reg, include_stack.cus.clone());
let (screw_rust, ws) = script_raw.up_script(&mut helper)?;
warnings = ws;
Ok(screw_rust)
})
.map_err(|e: rental::RentalError<Error, Box<String>>| e.0)?;
Ok(Self {
script: rented_script,
source: script,
warnings,
})
}(&mut include_stack);
r.map_err(|error| CompilerError {
error,
cus: include_stack.into_cus(),
})
}
pub fn docs(&self) -> &Docs<'_> {
&self.script.suffix().docs
}
#[cfg_attr(tarpaulin, skip)]
pub fn highlight_script_with<H: Highlighter>(script: &str, h: &mut H) -> io::Result<()> {
let tokens: Vec<_> = lexer::Tokenizer::new(&script)
.filter_map(Result::ok)
.collect();
h.highlight(None, &tokens)
}
#[cfg_attr(tarpaulin, skip)]
pub fn highlight_preprocess_script_with<H: Highlighter>(
file_name: &str,
script: &'script str,
h: &mut H,
) -> io::Result<()> {
let mut s = script.to_string();
let mut include_stack = lexer::IncludeStack::default();
let cu = include_stack.push(file_name)?;
let tokens: Vec<_> = lexer::Preprocessor::preprocess(
&crate::path::load(),
&file_name,
&mut s,
cu,
&mut include_stack,
)?
.into_iter()
.filter_map(Result::ok)
.collect();
h.highlight(Some(file_name), &tokens)
}
pub fn format_error_from_script<H: Highlighter>(
script: &str,
h: &mut H,
CompilerError { error, cus }: &CompilerError,
) -> io::Result<()> {
Self::format_error_from_script_and_cus(script, h, error, cus)
}
fn format_error_from_script_and_cus<H: Highlighter>(
script: &str,
h: &mut H,
error: &Error,
cus: &[lexer::CompilationUnit],
) -> io::Result<()> {
if let (Some(Range(start, end)), _) = error.context() {
let cu = error.cu();
if cu == 0 {
let tokens: Vec<_> = lexer::Tokenizer::new(&script)
.filter_map(Result::ok)
.collect();
h.highlight_runtime_error(None, &tokens, start, end, Some(error.into()))?;
} else if let Some(cu) = cus.get(cu) {
let script = std::fs::read_to_string(cu.file_path())?;
let tokens: Vec<_> = lexer::Tokenizer::new(&script)
.filter_map(Result::ok)
.collect();
h.highlight_runtime_error(
cu.file_path().to_str(),
&tokens,
start,
end,
Some(error.into()),
)?;
} else {
write!(h.get_writer(), "Error: {}", error)?;
};
} else {
write!(h.get_writer(), "Error: {}", error)?;
}
h.finalize()
}
pub fn format_warnings_with<H: Highlighter>(&self, h: &mut H) -> io::Result<()> {
let mut warnings = self.warnings.clone();
warnings.sort();
warnings.dedup();
for w in &warnings {
let tokens: Vec<_> = lexer::Tokenizer::new(&self.source)
.filter_map(Result::ok)
.collect();
h.highlight_runtime_error(None, &tokens, w.outer.0, w.outer.1, Some(w.into()))?;
}
h.finalize()
}
pub fn format_error(&self, e: &Error) -> String {
let mut h = DumbHighlighter::default();
if self.format_error_with(&mut h, &e).is_ok() {
h.to_string()
} else {
format!("Failed to extract code for error: {}", e)
}
}
pub fn format_error_with<H: Highlighter>(&self, h: &mut H, e: &Error) -> io::Result<()> {
Self::format_error_from_script_and_cus(
&self.source,
h,
e,
&self.script.suffix().node_meta.cus,
)
}
pub fn run(
&'script self,
context: &'run EventContext,
aggr: AggrType,
event: &'run mut Value<'event>,
state: &'run mut Value<'static>,
meta: &'run mut Value<'event>,
) -> Result<Return<'event>> {
self.script.suffix().run(context, aggr, event, state, meta)
}
}