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,
program: &Program,
runtime: &mut Runtime,
args: usize,
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 = runtime.reader.tell();
if let Some((reader_end, result)) = runtime.memo.get(&(reader_start.offset, id)) {
runtime.reader.reset(*reader_end);
return result.clone();
}
}
let mut context = Context::new(
program,
self,
runtime,
self.locals,
args,
if main { self.locals } else { 0 }, depth,
);
if !main {
if args > self.signature.len() {
return Err(match self.signature.len() {
0 => format!(
"{}() doesn't accept any arguments ({} given)",
self.name, args
),
1 => format!(
"{}() takes exactly one argument ({} given)",
self.name, args
),
_ => format!(
"{}() expected at most {} arguments ({} given)",
self.name,
self.signature.len(),
args
),
}
.into())
.into();
}
for (i, arg) in (&self.signature[args..]).iter().enumerate() {
let var = &mut context.runtime.stack[context.stack_start + args + 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(context.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();
}
}
} else
{
assert!(self.signature.len() == 0)
}
for i in 0..self.locals {
if let Capture::Empty = context.runtime.stack[context.stack_start + i] {
context.runtime.stack[context.stack_start + i] =
Capture::Value(crate::value!(void), None, 0);
}
}
let result = if let Some(true) = self.consuming {
let mut reader_end = context.frame0().reader_start;
let mut result = Err(Reject::Next);
context.runtime.memo.insert(
(context.frame0().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.runtime.reader.tell();
if loop_end.offset <= reader_end.offset {
break;
}
result = loop_result;
reader_end = loop_end;
context.runtime.memo.insert(
(context.frame0().reader_start.offset, id),
(reader_end, result.clone()),
);
context.runtime.reader.reset(context.frame0().reader_start);
context.runtime.stack.truncate(context.stack_start); context
.runtime
.stack
.resize(context.frame0().capture_start, Capture::Empty);
}
context.runtime.reader.reset(reader_end);
result
} else {
let result = context.run(main);
if !main && self.consuming.is_some() {
context.runtime.memo.insert(
(context.frame0().reader_start.offset, id),
(context.runtime.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) => {
let len = args.len();
for arg in args {
context.runtime.stack.push(Capture::Value(arg, None, 0));
}
self.0.borrow().run(
context.program,
context.runtime,
len,
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.program,
context.runtime,
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())
}
}