use std::cell::RefCell;
use std::rc::Rc;
use super::{BoxedObject, Dict, Object, RefValue};
use crate::error::Error;
use crate::vm::*;
#[derive(Debug)]
pub struct Parselet {
pub name: String, pub(crate) consuming: Option<bool>, pub(crate) severity: u8, signature: Vec<(String, Option<usize>)>, pub(crate) locals: usize, pub(crate) begin: Vec<Op>, pub(crate) end: Vec<Op>, pub(crate) body: Vec<Op>, }
impl Parselet {
pub(crate) fn new(
name: Option<String>,
consuming: Option<bool>,
severity: u8,
signature: Vec<(String, Option<usize>)>,
locals: usize,
begin: Vec<Op>,
end: Vec<Op>,
body: Vec<Op>,
) -> Self {
assert!(
signature.len() <= locals,
"signature may not be longer than locals..."
);
let mut ret = Self {
name: name.unwrap_or(String::new()),
consuming,
severity,
signature,
locals,
begin,
end,
body,
};
if ret.name.is_empty() {
ret.name = format!("parselet_{:x}", &ret as *const Parselet as usize);
}
ret
}
pub fn run(
&self,
thread: &mut Thread,
mut args: Vec<Capture>,
mut nargs: Option<Dict>,
main: bool,
depth: usize,
) -> Result<Accept, Reject> {
let id = self as *const Parselet as usize;
if self.consuming.is_some() {
let reader_start = thread.reader.tell();
if let Some((reader_end, result)) = thread.memo.get(&(reader_start.offset, id)) {
thread.reader.reset(*reader_end);
return result.clone();
}
}
if main {
assert!(self.signature.is_empty());
}
let args_len = args.len();
if args_len > self.signature.len() {
return Err(match self.signature.len() {
0 => format!(
"{}() doesn't accept any arguments ({} given)",
self.name, args_len
),
1 => format!(
"{}() takes exactly one argument ({} given)",
self.name, args_len
),
_ => format!(
"{}() expected at most {} arguments ({} given)",
self.name,
self.signature.len(),
args_len
),
}
.into())
.into();
}
if main {
thread
.globals
.resize_with(self.locals, || crate::value!(void));
} else {
args.resize(self.locals, Capture::Empty);
}
for (i, arg) in (&self.signature[args_len..]).iter().enumerate() {
let var = &mut args[args_len + i];
if matches!(var, Capture::Empty) {
if let Some(ref mut nargs) = nargs {
if let Some(value) = nargs.remove_str(&arg.0) {
*var = Capture::Value(value, None, 0);
continue;
}
}
if let Some(addr) = arg.1 {
*var = Capture::Value(thread.program.statics[addr].clone(), None, 0);
continue;
}
return Error::new(
None,
format!("{}() expected argument '{}'", self.name, arg.0),
)
.into();
}
}
if let Some(mut nargs) = nargs {
if let Some((name, _)) = nargs.pop() {
return Err(match nargs.len() {
0 => format!(
"{}() doesn't accept named argument '{}'",
self.name,
name.to_string()
),
n => format!(
"{}() doesn't accept named arguments ({} given)",
self.name,
n + 1
),
}
.into())
.into();
}
}
let mut context = Context::new(thread, self, depth, args);
let reader_start = context.frame0().reader_start;
let result = if let Some(true) = self.consuming {
let mut reader_end = reader_start;
let mut result = Err(Reject::Next);
context
.thread
.memo
.insert((reader_start.offset, id), (reader_end, result.clone()));
loop {
let loop_result = context.run(main);
match loop_result {
Err(Reject::Main) | Err(Reject::Error(_)) => {
result = loop_result;
break;
}
Err(_) => break,
_ => {}
}
let loop_end = context.thread.reader.tell();
if loop_end.offset <= reader_end.offset {
break;
}
result = loop_result;
reader_end = loop_end;
context
.thread
.memo
.insert((reader_start.offset, id), (reader_end, result.clone()));
context.thread.reader.reset(reader_start);
context.stack.clear();
context
.stack
.resize(context.frame0().capture_start, Capture::Empty);
}
context.thread.reader.reset(reader_end);
result
} else {
let result = context.run(main);
if self.consuming.is_some() {
context.thread.memo.insert(
(reader_start.offset, id),
(context.thread.reader.tell(), result.clone()),
);
}
result
};
result
}
}
impl From<Parselet> for RefValue {
fn from(parselet: Parselet) -> Self {
RefValue::from(Box::new(ParseletRef(Rc::new(RefCell::new(parselet)))) as BoxedObject)
}
}
#[derive(Clone, Debug)]
pub struct ParseletRef(pub Rc<RefCell<Parselet>>);
impl Object for ParseletRef {
fn id(&self) -> usize {
&*self.0.borrow() as *const Parselet as usize
}
fn name(&self) -> &'static str {
"parselet"
}
fn repr(&self) -> String {
format!("<{} {}>", self.name(), self.0.borrow().name)
}
fn is_callable(&self, without_arguments: bool) -> bool {
let parselet = self.0.borrow();
if without_arguments {
parselet.signature.len() == 0 || parselet.signature.iter().all(|arg| arg.1.is_some())
} else {
true
}
}
fn is_consuming(&self) -> bool {
self.0.borrow().consuming.is_some()
}
fn call(
&self,
context: Option<&mut Context>,
args: Vec<RefValue>,
nargs: Option<Dict>,
) -> Result<Accept, Reject> {
match context {
Some(context) => self.0.borrow().run(
context.thread,
args.into_iter()
.map(|arg| Capture::Value(arg, None, 0))
.collect(),
nargs,
false,
context.depth + 1,
),
None => panic!("{} needs a context to operate", self.repr()),
}
}
fn call_direct(
&self,
context: &mut Context,
args: usize,
nargs: Option<Dict>,
) -> Result<Accept, Reject> {
self.0.borrow().run(
context.thread,
context.stack.split_off(context.stack.len() - args),
nargs,
false,
context.depth + 1,
)
}
}
impl PartialEq for ParseletRef {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl PartialOrd for ParseletRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.id().partial_cmp(&other.id())
}
}