use std::{path::PathBuf, sync::Arc};
use colored::Colorize;
use imbl::vector;
use lazy_static::lazy_static;
use nanoid::nanoid;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{model::{DataRef, Graph, NodeRef, PROTOTYPE_EXTENDS_ATTR, PROTOTYPE_TYPE_ATTR, Profile, SId, libraries::prof::insert_profile_lib}, runtime::{Error, Runtime, Type, Val, Variable, instruction::Instruction, instructions::call::FuncCall, proc::Process}};
lazy_static! {
static ref PARSE_ID: SId = SId::from("parse");
}
pub struct ParseContext<'ctx> {
pub graph: &'ctx mut Graph,
pub runtime: Runtime,
pub profile: Profile,
pub init_funcs: Vec<DataRef>,
relative_import_stack: Vec<PathBuf>,
seen_import_paths: FxHashMap<NodeRef, FxHashSet<String>>,
}
impl<'ctx> ParseContext<'ctx> {
pub fn new(graph: &'ctx mut Graph, profile: Profile) -> Self {
let mut runtime = Runtime::default();
let mut process = Process::default();
process.env.pid = PARSE_ID.clone();
runtime.done.insert(process.env.pid.clone(), process);
insert_profile_lib(graph, &profile);
Self {
graph,
runtime,
profile,
init_funcs: Default::default(),
relative_import_stack: Default::default(),
seen_import_paths: Default::default(),
}
}
pub fn parse_from_file(&mut self, format: &str, path: &str, node: Option<NodeRef>) -> Result<(), Error> {
let node = node.unwrap_or(self.self_ptr());
let mut path = path.to_string();
path = self.create_import_path(format, &path).expect("could not create Stof import path");
if !self.fresh_import_for_node(&node, &path, format) {
self.pop_relative_import_stack();
return Ok(()); }
self.push_self_node(node);
if let Some(format_impl) = self.graph.get_format(format) {
match format_impl.parser_import(format, &path, self) {
Ok(_) => {},
Err(mut error) => {
self.pop_relative_import_stack();
self.pop_self();
match &mut error {
Error::ParseError(error) => {
error.file_path = Some(path);
},
_ => {}
}
return Err(error);
}
}
}
self.pop_self();
self.pop_relative_import_stack();
Ok(())
}
fn create_import_path(&mut self, format: &str, path: &str) -> Result<String, Error> {
if self.relative_import_stack.is_empty() {
if let Ok(working) = std::env::current_dir() {
self.relative_import_stack.push(working);
}
}
let mut path = path.replace("@", "stof/").replace(" ", "");
if path.starts_with(".") {
if self.relative_import_stack.is_empty() {
return Err(Error::RelativeImportWithoutContext);
}
let mut prefix = self.relative_import_stack.last().unwrap().as_path();
while path.starts_with("../") && !prefix.parent().is_some() {
prefix = prefix.parent().unwrap();
path = path.strip_prefix("../").unwrap().to_string();
}
path = path.trim_start_matches("./").to_string();
let prefix_path = prefix.as_os_str().to_os_string().into_string();
if prefix_path.is_err() { return Err(Error::ImportOsStringError); }
path = format!("{}/{}", prefix_path.unwrap(), path.trim_start_matches("/").trim_end_matches("/"));
}
if format == "stof" && !path.ends_with(".stof") && !path.ends_with(".json") {
path.push_str(".stof");
}
let mut relative_buffer = PathBuf::from(&path);
relative_buffer.pop();
self.relative_import_stack.push(relative_buffer);
Ok(path)
}
pub fn push_relative_import_stack_file(&mut self, file_path: &str) {
let mut relative_buffer = PathBuf::from(file_path);
relative_buffer.pop();
self.relative_import_stack.push(relative_buffer);
}
pub fn pop_relative_import_stack(&mut self) {
self.relative_import_stack.pop();
}
fn fresh_import_for_node(&mut self, node: &NodeRef, path: &str, format: &str) -> bool {
let cmp = format!("{format}{path}"); if let Some(seen) = self.seen_import_paths.get_mut(node) {
if seen.contains(&cmp) {
return false;
}
seen.insert(cmp);
} else {
let mut set = FxHashSet::default();
set.insert(cmp);
self.seen_import_paths.insert(node.clone(), set);
}
true
}
pub fn parse_proc<'a>(&'a mut self) -> &'a mut Process {
self.runtime.done.get_mut(&PARSE_ID).unwrap()
}
pub fn self_ptr(&mut self) -> NodeRef {
let proc = self.parse_proc();
if proc.env.self_stack.len() > 0 {
proc.env.self_ptr()
} else {
self.graph.ensure_main_root()
}
}
pub fn push_root(&mut self, name: Option<String>, cid: Option<SId>) {
let mut obj_name = nanoid!(12);
if let Some(name) = name {
obj_name = name;
}
let nref;
if let Some(id) = cid {
if id.node_exists(&self.graph) {
nref = self.graph.insert_root(&obj_name); } else {
nref = self.graph.insert_node_id(&obj_name, id, None, false);
}
} else {
nref = self.graph.insert_root(&obj_name);
}
let proc = self.parse_proc();
proc.env.self_stack.push(nref);
}
pub fn post_init_obj(&mut self, value: &Variable, attributes: &mut FxHashMap<String, Val>) -> Result<(), Error> {
if let Some(extends_attr) = attributes.get(PROTOTYPE_EXTENDS_ATTR.as_str()) {
if let Some(obj) = value.try_obj() {
let context = self.self_ptr();
match extends_attr {
Val::Str(typename) => {
let cast_type = Type::Obj(typename.as_str().into());
Val::Obj(obj).cast(&cast_type, &mut self.graph, Some(context))?;
},
Val::Obj(proto) => {
let cast_type = Type::Obj(proto.clone());
Val::Obj(obj).cast(&cast_type, &mut self.graph, Some(context))?;
},
_ => {}
}
}
}
Ok(())
}
pub fn push_self(&mut self, name: &str, attributes: &mut FxHashMap<String, Val>, id: Option<SId>) -> Variable {
let parent = self.self_ptr();
let nref;
if let Some(cid) = id {
if cid.node_exists(&self.graph) {
nref = self.graph.insert_node(name, Some(parent), false); } else {
nref = self.graph.insert_node_id(name, cid, Some(parent), false);
}
} else {
nref = self.graph.insert_node(name, Some(parent), false);
}
if let Some(node) = nref.node_mut(&mut self.graph) {
node.attributes = attributes.clone(); }
if let Some(type_attr) = attributes.get(PROTOTYPE_TYPE_ATTR.as_str()) {
match type_attr {
Val::Str(name) => {
self.graph.insert_type(name.as_str(), &nref);
},
_ => {
self.graph.insert_type(name, &nref);
}
}
}
let proc = self.parse_proc();
proc.env.self_stack.push(nref.clone());
Variable::val(Val::Obj(nref))
}
pub fn push_self_node(&mut self, node: NodeRef) {
let proc = self.parse_proc();
proc.env.self_stack.push(node);
}
pub fn pop_self(&mut self) {
let proc = self.parse_proc();
if proc.env.self_stack.len() > 1 {
proc.env.self_stack.pop();
}
}
fn reset_proc(&mut self) {
self.runtime.clear();
let mut process = Process::default();
process.env.pid = PARSE_ID.clone();
self.runtime.done.insert(process.env.pid.clone(), process);
}
pub fn eval(&mut self, instruction: Arc<dyn Instruction>) -> Result<Val, Error> {
let mut proc = self.runtime.done.remove(&PARSE_ID).unwrap();
proc.env.call_stack.clear();
proc.env.new_stack.clear();
proc.env.stack.clear();
proc.env.table.clear();
proc.instructions.clear();
proc.result = None;
proc.error = None;
proc.waiting = None;
proc.instructions.push(instruction);
self.runtime.push_running_proc(proc, &mut self.graph);
self.runtime.run_to_complete(&mut self.graph);
if let Some(proc) = self.runtime.done.get_mut(&PARSE_ID) {
if let Some(res) = proc.result.take() {
Ok(res.get())
} else {
Ok(Val::Void)
}
} else if let Some(mut proc) = self.runtime.errored.remove(&PARSE_ID) {
let res;
if let Some(err) = proc.error.take() {
res = Err(err);
} else {
res = Err(Error::NotImplemented);
}
self.runtime.done.insert(proc.env.pid.clone(), proc);
res
} else {
self.reset_proc();
Err(Error::NotImplemented)
}
}
}
impl<'ctx> Drop for ParseContext<'ctx> {
fn drop(&mut self) {
if self.profile.docs {
self.graph.insert_lib_docs();
}
if self.init_funcs.len() > 0 {
for init in self.init_funcs.clone() {
let ins: Arc<dyn Instruction> = Arc::new(FuncCall {
as_ref: false,
cnull: false,
stack: false,
func: Some(init),
search: None,
args: vector![],
oself: None,
});
self.runtime.push_running_proc(Process::from(ins), &mut self.graph);
}
self.runtime.err_callback = Some(Box::new(|graph, errored| {
if errored.env.call_stack.len() > 0 {
let func_ref = errored.env.call_stack.first().unwrap();
if let Some(name) = func_ref.data_name(graph) {
let mut func_path = String::from("<unknown>");
for node in func_ref.data_nodes(graph) { func_path = node.node_path(graph, true).unwrap().join("."); }
let mut err_str = String::from("<unknown>");
if let Some(err) = &errored.error {
err_str = err.to_string();
}
println!("{} {} {} {} {}\n\t{}\n", "init".purple(), func_path.italic().dimmed(), name.as_ref().italic().blue(), "...".dimmed(), "failed".bold().red(), err_str.bold().bright_cyan());
}
}
true
}));
self.runtime.run_to_complete(&mut self.graph);
}
}
}