mirror of
https://github.com/Dummi26/mers.git
synced 2025-12-15 03:36:16 +01:00
full rewrite, kinda works
This commit is contained in:
1550
mers/Cargo.lock
generated
1550
mers/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,10 @@
|
||||
[package]
|
||||
name = "mers"
|
||||
version = "0.2.3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "mers_libs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
edit = "0.1.4"
|
||||
notify = "5.1.0"
|
||||
regex = "1.7.2"
|
||||
static_assertions = "1.1.0"
|
||||
nu-plugin = { version = "0.79.0", optional = true }
|
||||
nu-protocol = { version = "0.79.0", features = ["plugin"], optional = true }
|
||||
colorize = "0.1.0"
|
||||
|
||||
[features]
|
||||
# default = ["nushell_plugin"]
|
||||
nushell_plugin = ["dep:nu-plugin", "dep:nu-protocol"]
|
||||
|
||||
[profile.nushellplugin]
|
||||
inherits = "release"
|
||||
mers_lib = { path = "../mers_lib" }
|
||||
clap = { version = "4.3.19", features = ["derive"] }
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
use std::io::{Stdin, StdinLock, Write};
|
||||
|
||||
use crate::lang::val_type::VType;
|
||||
|
||||
use super::libs::{
|
||||
comms::{self, ByteData, ByteDataA, Message, RespondableMessage},
|
||||
LibInitInfo, LibInitReq,
|
||||
};
|
||||
|
||||
pub struct MyLib {
|
||||
// name: String,
|
||||
version: (u32, u32),
|
||||
// description: String,
|
||||
// functions: Vec<(String, Vec<VType>, VType)>,
|
||||
pub callbacks: Callbacks,
|
||||
enum_variants: Vec<(String, usize)>,
|
||||
stdin: StdinLock<'static>,
|
||||
#[allow(unused)]
|
||||
stdin_no_lock: Stdin,
|
||||
}
|
||||
impl MyLib {
|
||||
pub fn new(
|
||||
name: String,
|
||||
version: (u32, u32),
|
||||
description: String,
|
||||
functions: Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
) -> Self {
|
||||
let stdout_no_lock = std::io::stdout();
|
||||
let stdin_no_lock = std::io::stdin();
|
||||
let mut stdout = stdout_no_lock.lock();
|
||||
let mut stdin = stdin_no_lock.lock();
|
||||
// comms version
|
||||
stdout
|
||||
.write_all(1u128.as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
let init_req: LibInitReq = (version.0, version.1, name, description, functions);
|
||||
stdout
|
||||
.write_all(init_req.as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
let enum_variants = LibInitInfo::from_byte_data(&mut stdin).unwrap();
|
||||
Self {
|
||||
// name: name.clone(),
|
||||
version,
|
||||
// description: description.clone(),
|
||||
// functions: functions.clone(),
|
||||
callbacks: Callbacks::empty(),
|
||||
enum_variants,
|
||||
stdin,
|
||||
stdin_no_lock,
|
||||
}
|
||||
}
|
||||
pub fn get_enums(&self) -> &Vec<(String, usize)> {
|
||||
&self.enum_variants
|
||||
}
|
||||
fn get_one_msg(&mut self) -> Result<Result<(), Message>, std::io::Error> {
|
||||
let id = u128::from_byte_data(&mut self.stdin)?;
|
||||
let message = Message::from_byte_data(&mut self.stdin)?;
|
||||
Ok(match message {
|
||||
Message::RunFunction(msg) => self
|
||||
.callbacks
|
||||
.run_function
|
||||
.run(Respondable::new(id, msg))
|
||||
.map_err(|e| Message::RunFunction(e.msg)),
|
||||
})
|
||||
}
|
||||
pub fn get_next_unhandled_message(&mut self) -> Result<(), Message> {
|
||||
loop {
|
||||
match self.get_one_msg() {
|
||||
Ok(Ok(())) => {}
|
||||
// unhandled message. return it to be handeled or included in the error
|
||||
Ok(Err(msg)) => return Err(msg),
|
||||
// i/o error, probably because mers exited. return successfully.
|
||||
Err(_e) => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Respondable<M> {
|
||||
id: u128,
|
||||
pub msg: M,
|
||||
}
|
||||
impl<M> Respondable<M> {
|
||||
fn new(id: u128, msg: M) -> Self {
|
||||
Self { id, msg }
|
||||
}
|
||||
}
|
||||
impl<M> Respondable<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub fn respond(self, with: M::With) {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout.write_all(&self.id.as_byte_data_vec()).unwrap();
|
||||
stdout
|
||||
.write_all(&self.msg.respond(with).as_byte_data_vec())
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
}
|
||||
impl<M> Respondable<M>
|
||||
where
|
||||
M: Into<Message>,
|
||||
{
|
||||
pub fn to_general(self) -> Respondable<Message> {
|
||||
Respondable::new(self.id, self.msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Callbacks {
|
||||
pub run_function: Callback<comms::run_function::Message>,
|
||||
}
|
||||
impl Callbacks {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
run_function: Callback::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct Callback<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub nonconsuming: Vec<Box<dyn FnMut(&M)>>,
|
||||
pub consuming: Option<Box<dyn FnMut(Respondable<M>)>>,
|
||||
}
|
||||
impl<M> Callback<M>
|
||||
where
|
||||
M: RespondableMessage,
|
||||
{
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
nonconsuming: vec![],
|
||||
consuming: None,
|
||||
}
|
||||
}
|
||||
/// If the event was handled by a consuming function, returns Ok(r) where r is the returned value from the consuming function.
|
||||
/// If it wasn't handled (or only handled by nonconsuming functions), Err(m) is returned, giving ownership of the original message back to the caller for further handling.
|
||||
pub fn run(&mut self, msg: Respondable<M>) -> Result<(), Respondable<M>> {
|
||||
for f in self.nonconsuming.iter_mut() {
|
||||
f(&msg.msg);
|
||||
}
|
||||
if let Some(f) = self.consuming.as_mut() {
|
||||
Ok(f(msg))
|
||||
} else {
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/// creates a temporary file, then opens it in the user's default editor. watches the fs for changes to the file and acts upon them.
|
||||
pub mod fs_watcher {
|
||||
use notify::Watcher;
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use crate::lang::fmtgs::FormatGs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
/// on each file change, recompiles and runs the code. lets people experiment with mers without having to put a file anywhere
|
||||
pub fn playground(spawn_new_terminal_for_editor: bool) -> Result<(), Error> {
|
||||
main(
|
||||
spawn_new_terminal_for_editor,
|
||||
"// Welcome to mers! (interactive mode)
|
||||
|
||||
// put your name here, then save the file to run the script.
|
||||
your_name = \"\"
|
||||
greeting = \"Hello, {0}!\".format(your_name)
|
||||
println(greeting)
|
||||
",
|
||||
Box::new(|temp_file: &PathBuf| {
|
||||
println!();
|
||||
if let Ok(file_contents) = fs::read_to_string(&temp_file) {
|
||||
let mut file =
|
||||
crate::parsing::file::File::new(file_contents, temp_file.to_path_buf());
|
||||
match crate::parsing::parse::parse(&mut file) {
|
||||
Ok(func) => {
|
||||
println!(" - - - - -");
|
||||
let output = func.run(vec![]);
|
||||
println!(" - - - - -");
|
||||
println!("{}", output);
|
||||
}
|
||||
Err(e) => println!("{}", e.with_file(&file)),
|
||||
}
|
||||
} else {
|
||||
println!("can't read file at {:?}!", temp_file);
|
||||
std::process::exit(105);
|
||||
}
|
||||
}),
|
||||
)?
|
||||
.0
|
||||
.join()
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// when the file is changed, calls the provided closure with the file's path.
|
||||
/// returns a JoinHandle which will finish when the user closes the editor and the file that is being used.
|
||||
pub fn main(
|
||||
spawn_new_terminal_for_editor: bool,
|
||||
initial_file_contents: &str,
|
||||
mut on_file_change: Box<dyn FnMut(&PathBuf) + Send>,
|
||||
) -> Result<(JoinHandle<()>, PathBuf), Error> {
|
||||
let temp_file_edit = edit::Builder::new().suffix(".mers").tempfile().unwrap();
|
||||
let temp_file = temp_file_edit.path().to_path_buf();
|
||||
eprintln!(
|
||||
"Using temporary file at {temp_file:?}. Save the file to update the output here."
|
||||
);
|
||||
if let Ok(_) = std::fs::write(&temp_file, initial_file_contents) {
|
||||
if let Ok(mut watcher) = {
|
||||
let temp_file = temp_file.clone();
|
||||
// the file watcher
|
||||
notify::recommended_watcher(move |event: Result<notify::Event, notify::Error>| {
|
||||
if let Ok(event) = event {
|
||||
match &event.kind {
|
||||
notify::EventKind::Modify(notify::event::ModifyKind::Data(_)) => {
|
||||
on_file_change(&temp_file);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
})
|
||||
} {
|
||||
if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) {
|
||||
let out = if spawn_new_terminal_for_editor {
|
||||
if let Ok(term) = std::env::var("TERM") {
|
||||
let editor = edit::get_editor().unwrap();
|
||||
eprintln!("launching \"{term} -e {editor:?} {temp_file:?}...");
|
||||
let mut editor = std::process::Command::new(term)
|
||||
.arg("-e")
|
||||
.arg(&editor)
|
||||
.arg(&temp_file)
|
||||
.spawn()
|
||||
.unwrap();
|
||||
(
|
||||
thread::spawn(move || {
|
||||
// wait for the editor to finish
|
||||
editor.wait().unwrap();
|
||||
// stop the watcher (this is absolutely necessary because it also moves the watcher into the closure,
|
||||
// which prevents it from being dropped too early)
|
||||
drop(watcher);
|
||||
// close and remove the temporary file
|
||||
temp_file_edit.close().unwrap();
|
||||
}),
|
||||
temp_file,
|
||||
)
|
||||
} else {
|
||||
return Err(Error(format!("TERM environment variable not set.")));
|
||||
}
|
||||
} else {
|
||||
let tf = temp_file.clone();
|
||||
(
|
||||
thread::spawn(move || {
|
||||
edit::edit_file(temp_file).unwrap();
|
||||
drop(watcher);
|
||||
temp_file_edit.close().unwrap();
|
||||
}),
|
||||
tf,
|
||||
)
|
||||
};
|
||||
Ok(out)
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Cannot watch the file at \"{:?}\" for hot-reload.",
|
||||
temp_file
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Error(format!(
|
||||
"Cannot use filesystem watcher for hot-reload."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(Error(format!("could not write file \"{:?}\".", temp_file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,117 +0,0 @@
|
||||
use std::{fmt::Display, fs};
|
||||
|
||||
use crate::parsing::{
|
||||
file::File,
|
||||
parse::{self, ParseError, ScriptError},
|
||||
};
|
||||
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::{code_runnable::RScript, val_data::VData};
|
||||
|
||||
// macro format is !(macro_type [...])
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Macro {
|
||||
/// Compiles and executes the provided mers code at compile-time and inserts the value
|
||||
StaticMers(VData),
|
||||
}
|
||||
|
||||
pub fn parse_macro(file: &mut File) -> Result<Macro, MacroError> {
|
||||
file.skip_whitespaces();
|
||||
let macro_type = file.collect_to_whitespace();
|
||||
Ok(match macro_type.as_str() {
|
||||
"mers" => Macro::StaticMers({
|
||||
let code = parse_mers_code(file)?;
|
||||
let mut args = vec![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if let Some(')') = file.peek() {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
args.push(parse_string_val(file));
|
||||
}
|
||||
let val = code.run(
|
||||
args.into_iter()
|
||||
.map(|v| VDataEnum::String(v).to())
|
||||
.collect(),
|
||||
);
|
||||
if val.safe_to_share() {
|
||||
val
|
||||
} else {
|
||||
return Err(MacroError::StaticValueNotSafeToShare);
|
||||
}
|
||||
}),
|
||||
_ => return Err(MacroError::UnknownMacroType(macro_type)),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_string_val(file: &mut File) -> String {
|
||||
parse::implementation::parse_string_val(file, |ch| ch.is_whitespace() || ch == ')')
|
||||
}
|
||||
|
||||
fn parse_mers_code(file: &mut File) -> Result<RScript, MacroError> {
|
||||
file.skip_whitespaces();
|
||||
if let Some('{') = file.peek() {
|
||||
_ = file.next();
|
||||
match parse::parse(file) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(e.err.into()),
|
||||
}
|
||||
} else {
|
||||
let path = parse_string_val(file);
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("macro: mers: path: {path}");
|
||||
let path = crate::pathutil::path_from_string(path.as_str(), file.path(), false)
|
||||
.expect("can't include mers code because no file was found at that path");
|
||||
let mut file = File::new(
|
||||
fs::read_to_string(&path)
|
||||
.expect("can't include mers code because the file could not be read"),
|
||||
path.into(),
|
||||
);
|
||||
Ok(match parse::parse(&mut file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(e.err.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Macro {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::StaticMers(v) => write!(f, "mers {v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MacroError {
|
||||
MersStatementArgError(Box<ScriptError>),
|
||||
UnknownMacroType(String),
|
||||
StaticValueNotSafeToShare,
|
||||
}
|
||||
|
||||
impl Display for MacroError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MersStatementArgError(e) => write!(f, "error in mers statement argument: {e}"),
|
||||
Self::UnknownMacroType(t) => write!(
|
||||
f,
|
||||
"unknown macro type '{t}', try mers-include or mers-static."
|
||||
),
|
||||
Self::StaticValueNotSafeToShare => write!(f, "static value cannot safely be shared (cannot use value returned by mers-static in your code - maybe it was a reference, an enum, ...)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScriptError> for MacroError {
|
||||
fn from(value: ScriptError) -> Self {
|
||||
Self::MersStatementArgError(Box::new(value))
|
||||
}
|
||||
}
|
||||
impl From<ParseError> for MacroError {
|
||||
fn from(value: ParseError) -> Self {
|
||||
let value: ScriptError = value.into();
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use super::{
|
||||
code_macro::Macro, fmtgs::FormatGs, global_info::GlobalScriptInfo, val_data::VData,
|
||||
val_type::VType,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SStatementEnum {
|
||||
Value(VData),
|
||||
Tuple(Vec<SStatement>),
|
||||
List(Vec<SStatement>),
|
||||
Variable(String, bool),
|
||||
FunctionCall(String, Vec<SStatement>),
|
||||
FunctionDefinition(Option<String>, SFunction),
|
||||
Block(SBlock),
|
||||
If(SStatement, SStatement, Option<SStatement>),
|
||||
Loop(SStatement),
|
||||
For(SStatement, SStatement, SStatement),
|
||||
Switch(SStatement, Vec<(VType, SStatement, SStatement)>, bool),
|
||||
Match(Vec<(SStatement, SStatement, SStatement)>),
|
||||
IndexFixed(SStatement, usize),
|
||||
EnumVariant(String, SStatement),
|
||||
TypeDefinition(String, VType),
|
||||
Macro(Macro),
|
||||
}
|
||||
impl SStatementEnum {
|
||||
pub fn to(self) -> SStatement {
|
||||
SStatement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SStatement {
|
||||
pub derefs: usize,
|
||||
/// if the statement is a Variable that doesn't exist yet, it will be initialized.
|
||||
/// if it's a variable that exists, but is_ref is false, an error may show up: cannot dereference
|
||||
/// if the third value is true, the variable will always be initialized, shadowing previous mentions of the same name.
|
||||
pub output_to: Option<(Box<SStatement>, bool)>,
|
||||
pub statement: Box<SStatementEnum>,
|
||||
pub force_output_type: Option<VType>,
|
||||
}
|
||||
|
||||
impl SStatement {
|
||||
pub fn new(statement: SStatementEnum) -> Self {
|
||||
Self {
|
||||
derefs: 0,
|
||||
output_to: None,
|
||||
statement: Box::new(statement),
|
||||
force_output_type: None,
|
||||
}
|
||||
}
|
||||
pub fn output_to(mut self, statement: SStatement) -> Self {
|
||||
self.output_to = Some((Box::new(statement), false));
|
||||
self
|
||||
}
|
||||
/// like output_to, but always initializes the variable (shadows previous variables of the same name)
|
||||
pub fn initialize_to(mut self, statement: SStatement) -> Self {
|
||||
self.output_to = Some((Box::new(statement), true));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of code is a collection of statements.
|
||||
#[derive(Debug)]
|
||||
pub struct SBlock {
|
||||
pub statements: Vec<SStatement>,
|
||||
}
|
||||
impl SBlock {
|
||||
pub fn new(statements: Vec<SStatement>) -> Self {
|
||||
Self { statements }
|
||||
}
|
||||
}
|
||||
|
||||
// A function is a block of code that starts with some local variables as inputs and returns some value as its output. The last statement in the block will be the output.
|
||||
#[derive(Debug)]
|
||||
pub struct SFunction {
|
||||
pub inputs: Vec<(String, VType)>,
|
||||
pub statement: SStatement,
|
||||
}
|
||||
impl SFunction {
|
||||
pub fn new(inputs: Vec<(String, VType)>, statement: SStatement) -> Self {
|
||||
Self { inputs, statement }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
impl FormatGs for SStatementEnum {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Value(v) => v.fmtgs(f, info, form, file),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "{}", form.open_bracket(info, "[".to_owned()))?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, "]".to_owned()))
|
||||
}
|
||||
Self::List(v) => {
|
||||
write!(f, "{}", form.open_bracket(info, "[".to_owned()))?;
|
||||
for (_i, v) in v.iter().enumerate() {
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, "...]".to_owned()))
|
||||
}
|
||||
Self::Variable(var, reference) => {
|
||||
if *reference {
|
||||
write!(f, "{}", form.variable_ref_symbol(info, "&".to_owned()))?;
|
||||
}
|
||||
write!(f, "{}", form.variable(info, var.to_owned()))
|
||||
}
|
||||
Self::FunctionCall(func, args) => {
|
||||
write!(
|
||||
f,
|
||||
"{}{}",
|
||||
form.fncall(info, func.to_owned()),
|
||||
form.open_bracket(info, "(".to_owned())
|
||||
)?;
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "{}", form.close_bracket(info, ")".to_owned()))
|
||||
}
|
||||
Self::FunctionDefinition(name, func) => {
|
||||
if let Some(name) = name {
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
form.fndef_fn(info, "fn".to_owned()),
|
||||
form.fndef_name(info, name.to_owned())
|
||||
)?;
|
||||
}
|
||||
func.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Block(b) => b.fmtgs(f, info, form, file),
|
||||
Self::If(condition, yes, no) => {
|
||||
write!(f, "{} ", form.if_if(info, "if".to_owned()))?;
|
||||
condition.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
yes.fmtgs(f, info, form, file)?;
|
||||
if let Some(no) = no {
|
||||
write!(f, " {} ", form.if_else(info, "else".to_owned()))?;
|
||||
no.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::Loop(b) => {
|
||||
write!(f, "{} ", form.loop_loop(info, "loop".to_owned()))?;
|
||||
b.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::For(assign_to, i, b) => {
|
||||
write!(f, "{} ", form.loop_for(info, "for".to_owned()))?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
i.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
b.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Switch(var, arms, force) => {
|
||||
if *force {
|
||||
writeln!(
|
||||
f,
|
||||
"{} {var} {}",
|
||||
form.kw_switch(info, "switch!".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"{} {var} {}",
|
||||
form.kw_switch(info, "switch".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
}
|
||||
form.go_deeper();
|
||||
for (t, assign_to, action) in arms {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
action.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
Self::Match(arms) => {
|
||||
write!(
|
||||
f,
|
||||
"{} {}",
|
||||
form.kw_match(info, "match".to_owned()),
|
||||
form.open_bracket(info, "{".to_owned())
|
||||
)?;
|
||||
form.go_deeper();
|
||||
for (condition, assign_to, action) in arms {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
condition.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
assign_to.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
action.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
Self::IndexFixed(statement, index) => {
|
||||
statement.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".{index}")
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
write!(f, "{variant}: ")?;
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::TypeDefinition(name, t) => write!(f, "type {name} {t}"),
|
||||
Self::Macro(m) => {
|
||||
write!(f, "!({m})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for SStatementEnum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SStatement {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
// output output_to
|
||||
if let Some((opt, is_init)) = &self.output_to {
|
||||
write!(
|
||||
f,
|
||||
"{} {} ",
|
||||
opt.with(info, file),
|
||||
if *is_init { ":=" } else { "=" }
|
||||
)?;
|
||||
}
|
||||
// output self
|
||||
if let Some(force_opt) = &self.force_output_type {
|
||||
write!(f, "-> ")?;
|
||||
force_opt.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", "*".repeat(self.derefs))?;
|
||||
self.statement.fmtgs(f, info, form, file)?;
|
||||
write!(f, ",")
|
||||
}
|
||||
}
|
||||
impl Display for SStatement {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SFunction {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
write!(f, "(")?;
|
||||
for (i, (name, t)) in self.inputs.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " {name} ")?;
|
||||
} else {
|
||||
write!(f, "{name} ")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ") ")?;
|
||||
self.statement.fmtgs(f, info, form, file)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for SBlock {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self.statements.len() {
|
||||
0 => write!(f, "{{}}"),
|
||||
// 1 => self.statements[0].fmtgs(f, info, form, file),
|
||||
_ => {
|
||||
writeln!(f, "{}", form.open_bracket(info, "{".to_owned()))?;
|
||||
form.go_deeper();
|
||||
for statement in self.statements.iter() {
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
statement.fmtgs(f, info, form, file)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
form.go_shallower();
|
||||
write!(f, "{}", form.line_prefix())?;
|
||||
write!(f, "{}", form.close_bracket(info, "}".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins::BuiltinFunction,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
to_runnable::ToRunnableError,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RStatementEnum {
|
||||
Value(VData),
|
||||
Tuple(Vec<RStatement>),
|
||||
List(Vec<RStatement>),
|
||||
Variable(Arc<Mutex<(VData, VType)>>, bool),
|
||||
FunctionCall(Arc<RFunction>, Vec<RStatement>),
|
||||
BuiltinFunctionCall(BuiltinFunction, Vec<RStatement>),
|
||||
LibFunctionCall(usize, usize, Vec<RStatement>, VType),
|
||||
Block(RBlock),
|
||||
If(RStatement, RStatement, Option<RStatement>),
|
||||
Loop(RStatement),
|
||||
For(RStatement, RStatement, RStatement),
|
||||
Switch(RStatement, Vec<(VType, RStatement, RStatement)>, bool),
|
||||
Match(Vec<(RStatement, RStatement, RStatement)>),
|
||||
IndexFixed(RStatement, usize),
|
||||
EnumVariant(usize, RStatement),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RBlock {
|
||||
pub statements: Vec<RStatement>,
|
||||
}
|
||||
impl RBlock {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
let mut last = None;
|
||||
for statement in &self.statements {
|
||||
last = Some(statement.run(info));
|
||||
}
|
||||
if let Some(v) = last {
|
||||
v
|
||||
} else {
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
if let Some(last) = self.statements.last() {
|
||||
last.out(info)
|
||||
} else {
|
||||
VType {
|
||||
types: vec![VSingleType::Tuple(vec![])],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RFunction {
|
||||
pub statement: RFunctionType,
|
||||
pub out_map: Vec<(Vec<VType>, VType)>,
|
||||
}
|
||||
impl Debug for RFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.out_map)
|
||||
}
|
||||
}
|
||||
pub enum RFunctionType {
|
||||
/// ignores all args and returns some default value
|
||||
Dummy,
|
||||
Statement(Vec<Arc<Mutex<(VData, VType)>>>, RStatement, Vec<VType>),
|
||||
Func(Box<dyn Fn(&GSInfo, Vec<VData>) -> VData + Send + Sync>),
|
||||
}
|
||||
impl PartialEq for RFunction {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
impl Eq for RFunction {
|
||||
fn assert_receiver_is_total_eq(&self) {}
|
||||
}
|
||||
impl RFunction {
|
||||
pub fn run(&self, info: &GSInfo, args: Vec<VData>) -> VData {
|
||||
match &self.statement {
|
||||
RFunctionType::Dummy => VDataEnum::Bool(false).to(),
|
||||
RFunctionType::Statement(inputs, s, _) => {
|
||||
for (i, input) in inputs.iter().enumerate() {
|
||||
input.lock().unwrap().0.assign(args[i].clone_mut());
|
||||
}
|
||||
s.run(info)
|
||||
}
|
||||
RFunctionType::Func(f) => f(info, args),
|
||||
}
|
||||
}
|
||||
pub fn out_by_map(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
||||
let mut empty = true;
|
||||
let out =
|
||||
self.out_map
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == (input_types.len())
|
||||
&& fn_in.iter().zip(input_types.iter()).all(|(fn_in, arg)| {
|
||||
arg.types.iter().any(|t| t.fits_in_type(fn_in, info))
|
||||
})
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, info);
|
||||
}
|
||||
t
|
||||
});
|
||||
if empty {
|
||||
None
|
||||
} else {
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
pub fn out_all_by_map(&self, info: &GlobalScriptInfo) -> VType {
|
||||
// self.statement.out(info)
|
||||
self.out_map.iter().fold(VType::empty(), |mut t, (_, v)| {
|
||||
t.add_typesr(v, info);
|
||||
t
|
||||
})
|
||||
}
|
||||
pub fn out_by_statement(
|
||||
&self,
|
||||
input_types: &Vec<VType>,
|
||||
info: &GlobalScriptInfo,
|
||||
) -> Option<VType> {
|
||||
if let RFunctionType::Statement(inputs, statement, _) = &self.statement {
|
||||
let mut actual = Vec::with_capacity(inputs.len());
|
||||
// simulate these variable types
|
||||
for (fn_input, c_type) in inputs.iter().zip(input_types.iter()) {
|
||||
actual.push(std::mem::replace(
|
||||
&mut fn_input.lock().unwrap().1,
|
||||
c_type.clone(),
|
||||
));
|
||||
}
|
||||
// not get the return type if these were the actual types
|
||||
let out = statement.out(info);
|
||||
// reset
|
||||
for (fn_input, actual) in inputs.iter().zip(actual) {
|
||||
fn_input.lock().unwrap().1 = actual;
|
||||
}
|
||||
// return
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RStatement {
|
||||
// (_, derefs, is_init)
|
||||
pub derefs: usize,
|
||||
pub output_to: Option<(Box<RStatement>, bool)>,
|
||||
statement: Box<RStatementEnum>,
|
||||
pub force_output_type: Option<VType>,
|
||||
}
|
||||
impl RStatement {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
let out = self.statement.run(info);
|
||||
let mut o = if let Some((v, _is_init)) = &self.output_to {
|
||||
// // assigns a new VData to the variable's Arc<Mutex<_>>, so that threads which have captured the variable at some point
|
||||
// // won't be updated with its new value (is_init is set to true for initializations, such as in a loop - this can happen multiple times, but each should be its own variable with the same name)
|
||||
// if *is_init && *derefs == 0 {
|
||||
// Self::assign_to(out, v.run(info), info);
|
||||
// break 'init;
|
||||
// }
|
||||
let val = v.run(info);
|
||||
out.assign_to(val, info);
|
||||
// val.assign(out);
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
} else {
|
||||
out
|
||||
};
|
||||
for _ in 0..self.derefs {
|
||||
o = o.deref().expect("couldn't dereference! (run())");
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
// `a = b` evaluates to [] (don't change this - cloning is cheap but a = b should NEVER return a boolean because that will make if a = b {} errors way too likely.)
|
||||
if self.output_to.is_some() {
|
||||
return VType {
|
||||
types: vec![VSingleType::Tuple(vec![])],
|
||||
};
|
||||
}
|
||||
if let Some(t) = &self.force_output_type {
|
||||
return t.clone();
|
||||
}
|
||||
let mut o = self.statement.out(info);
|
||||
for _ in 0..self.derefs {
|
||||
o = o.dereference(info).expect("can't dereference (out())");
|
||||
}
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
impl RStatementEnum {
|
||||
pub fn run(&self, info: &GSInfo) -> VData {
|
||||
match self {
|
||||
Self::Value(v) => v.clone(),
|
||||
Self::Tuple(v) => {
|
||||
let mut w = vec![];
|
||||
for v in v {
|
||||
w.push(v.run(info));
|
||||
}
|
||||
VDataEnum::Tuple(w).to()
|
||||
}
|
||||
Self::List(v) => {
|
||||
let mut w = vec![];
|
||||
let mut out = VType { types: vec![] };
|
||||
for v in v {
|
||||
let val = v.run(info);
|
||||
out.add_types(val.out(), &info);
|
||||
w.push(val);
|
||||
}
|
||||
VDataEnum::List(out, w).to()
|
||||
}
|
||||
Self::Variable(v, is_ref) => {
|
||||
if *is_ref {
|
||||
VDataEnum::Reference(v.lock().unwrap().0.clone_mut()).to()
|
||||
} else {
|
||||
v.lock().unwrap().0.clone_data()
|
||||
}
|
||||
}
|
||||
Self::FunctionCall(func, args) => {
|
||||
func.run(info, args.iter().map(|s| s.run(info)).collect())
|
||||
}
|
||||
Self::BuiltinFunctionCall(v, args) => v.run(args, info),
|
||||
Self::LibFunctionCall(libid, fnid, args, _) => {
|
||||
info.libs[*libid].run_fn(*fnid, args.iter().map(|arg| arg.run(info)).collect())
|
||||
}
|
||||
Self::Block(b) => b.run(info),
|
||||
Self::If(c, t, e) => c.run(info).operate_on_data_immut(|v| {
|
||||
if let VDataEnum::Bool(v) = v {
|
||||
if *v {
|
||||
t.run(info)
|
||||
} else {
|
||||
if let Some(e) = e {
|
||||
e.run(info)
|
||||
} else {
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}),
|
||||
Self::Loop(c) => loop {
|
||||
// loops will break if the value matches.
|
||||
if let Some(break_val) = c.run(info).matches() {
|
||||
break break_val;
|
||||
}
|
||||
},
|
||||
Self::For(v, c, b) => {
|
||||
// matching values also break with value from a for loop.
|
||||
let vv = v.run(info);
|
||||
let in_loop = |c: VData| {
|
||||
c.assign_to(vv.clone_mut(), info);
|
||||
b.run(info)
|
||||
};
|
||||
let mut iter = c.run(info);
|
||||
if let Some(v) = iter.operate_on_data_immut(|c: &VDataEnum| {
|
||||
let mut oval = VDataEnum::Tuple(vec![]).to();
|
||||
match c {
|
||||
VDataEnum::Int(v) => {
|
||||
for i in 0..*v {
|
||||
if let Some(v) = in_loop(VDataEnum::Int(i).to()).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::String(v) => {
|
||||
for ch in v.chars() {
|
||||
if let Some(v) =
|
||||
in_loop(VDataEnum::String(ch.to_string()).to()).matches()
|
||||
{
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for v in v {
|
||||
if let Some(v) = in_loop(v.clone()).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(f) => loop {
|
||||
if let Some(v) = f.run(info, vec![]).matches() {
|
||||
if let Some(v) = in_loop(v).matches() {
|
||||
oval = v;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
VDataEnum::Reference(_r) => return None,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Some(oval)
|
||||
}) {
|
||||
v
|
||||
} else {
|
||||
// loop mutably
|
||||
iter.operate_on_data_mut(|c| match c {
|
||||
VDataEnum::Reference(r) => r.operate_on_data_mut(|c| match c {
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for v in v {
|
||||
if let Some(v) =
|
||||
in_loop(VDataEnum::Reference(v.clone_mut()).to()).matches()
|
||||
{
|
||||
return v;
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Self::Switch(switch_on, cases, _force) => {
|
||||
let switch_on = switch_on.run(info);
|
||||
let switch_on_type = switch_on.out();
|
||||
let mut out = VDataEnum::Tuple(vec![]).to();
|
||||
for (case_type, assign_to, case_action) in cases.iter() {
|
||||
if switch_on_type.fits_in(case_type, info).is_empty() {
|
||||
switch_on.assign_to(assign_to.run(info), info);
|
||||
out = case_action.run(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Match(cases) => 'm: {
|
||||
for (case_condition, assign_to, case_action) in cases {
|
||||
// [t] => Some(t), t => Some(t), [] | false => None
|
||||
if let Some(v) = case_condition.run(info).matches() {
|
||||
v.assign_to(assign_to.run(info), info);
|
||||
// let og = { std::mem::replace(&mut *match_on.lock().unwrap(), v) };
|
||||
let res = case_action.run(info);
|
||||
// *match_on.lock().unwrap() = og;
|
||||
break 'm res;
|
||||
}
|
||||
}
|
||||
VDataEnum::Tuple(vec![]).to()
|
||||
}
|
||||
Self::IndexFixed(st, i) => st.run(info).get(*i).unwrap(),
|
||||
Self::EnumVariant(e, v) => VDataEnum::EnumVariant(*e, Box::new(v.run(info))).to(),
|
||||
}
|
||||
}
|
||||
pub fn out(&self, info: &GlobalScriptInfo) -> VType {
|
||||
match self {
|
||||
Self::Value(v) => v.out(),
|
||||
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out(info)).collect()).into(),
|
||||
Self::List(v) => VSingleType::List({
|
||||
let mut types = VType { types: vec![] };
|
||||
for t in v {
|
||||
types.add_types(t.out(info), info);
|
||||
}
|
||||
types
|
||||
})
|
||||
.into(),
|
||||
Self::Variable(t, is_ref) => {
|
||||
if *is_ref {
|
||||
VSingleType::Reference(t.lock().unwrap().1.clone()).to()
|
||||
} else {
|
||||
t.lock().unwrap().1.clone()
|
||||
}
|
||||
}
|
||||
Self::FunctionCall(f, args) => f
|
||||
.out_by_map(&args.iter().map(|v| v.out(info)).collect(), info)
|
||||
.expect("invalid args for function -> can't determine output type"),
|
||||
Self::LibFunctionCall(.., out) => out.clone(),
|
||||
Self::Block(b) => b.out(info),
|
||||
Self::If(_, a, b) => {
|
||||
let mut out = a.out(info);
|
||||
if let Some(b) = b {
|
||||
out.add_types(b.out(info), info);
|
||||
} else {
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Loop(c) => c.out(info).matches(info).1,
|
||||
Self::For(_, _, b) => {
|
||||
let mut out = b.out(info).matches(info).1;
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
out
|
||||
}
|
||||
Self::BuiltinFunctionCall(f, args) => {
|
||||
f.returns(args.iter().map(|rs| rs.out(info)).collect(), info)
|
||||
}
|
||||
Self::Switch(switch_on, cases, force) => {
|
||||
let switch_on = switch_on.out(info).types;
|
||||
let _might_return_empty = switch_on.is_empty();
|
||||
let mut out = if *force {
|
||||
VType::empty()
|
||||
} else {
|
||||
VSingleType::Tuple(vec![]).to()
|
||||
};
|
||||
for _switch_on in switch_on {
|
||||
for (_on_type, _assign_to, case) in cases.iter() {
|
||||
out.add_types(case.out(info), info);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::Match(cases) => {
|
||||
let mut out = VType::empty();
|
||||
let mut can_fail_to_match = true;
|
||||
for (condition, _assign_to, action) in cases {
|
||||
out.add_types(action.out(info), info);
|
||||
if !condition.out(info).matches(info).0 {
|
||||
can_fail_to_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if can_fail_to_match {
|
||||
out.add_type(VSingleType::Tuple(vec![]), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::IndexFixed(st, i) => st.out(info).get(*i, info).unwrap(),
|
||||
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out(info)).to(),
|
||||
}
|
||||
}
|
||||
pub fn to(self) -> RStatement {
|
||||
RStatement {
|
||||
derefs: 0,
|
||||
output_to: None,
|
||||
statement: Box::new(self),
|
||||
force_output_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RScript {
|
||||
main: RFunction,
|
||||
pub info: GSInfo,
|
||||
}
|
||||
impl RScript {
|
||||
pub fn new(main: RFunction, info: GSInfo) -> Result<Self, ToRunnableError> {
|
||||
Ok(Self { main, info })
|
||||
}
|
||||
pub fn run(&self, args: Vec<VData>) -> VData {
|
||||
self.main.run(&self.info, args)
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use super::global_info::{ColorFormatMode, ColorFormatter, GlobalScriptInfo};
|
||||
|
||||
use colorize::AnsiColor;
|
||||
|
||||
pub enum Color {
|
||||
// Keep,
|
||||
Grey,
|
||||
Red,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
Cyan,
|
||||
Magenta,
|
||||
}
|
||||
impl Color {
|
||||
pub fn colorize(&self, s: String) -> String {
|
||||
match self {
|
||||
// Self::Keep => s,
|
||||
Self::Grey => s.grey().to_string(),
|
||||
Self::Red => s.red().to_string(),
|
||||
Self::Yellow => s.yellow().to_string(),
|
||||
Self::Green => s.green().to_string(),
|
||||
Self::Blue => s.blue().to_string(),
|
||||
Self::Cyan => s.cyan().to_string(),
|
||||
Self::Magenta => s.magenta().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatInfo {
|
||||
pub depth: usize,
|
||||
pub brackets: usize,
|
||||
}
|
||||
impl FormatInfo {
|
||||
fn color<F>(&self, info: Option<&GlobalScriptInfo>, color: F, s: String) -> String
|
||||
where
|
||||
F: Fn(&ColorFormatter) -> &Color,
|
||||
{
|
||||
if let Some(info) = info {
|
||||
let color = color(&info.formatter);
|
||||
match info.formatter.mode {
|
||||
ColorFormatMode::Plain => s,
|
||||
ColorFormatMode::Colorize => color.colorize(s),
|
||||
}
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
pub fn open_bracket(&mut self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
let o = self.color(
|
||||
info,
|
||||
|c| &c.bracket_colors[self.brackets % c.bracket_colors.len()],
|
||||
s,
|
||||
);
|
||||
self.brackets += 1;
|
||||
o
|
||||
}
|
||||
pub fn close_bracket(&mut self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.brackets -= 1;
|
||||
self.color(
|
||||
info,
|
||||
|c| &c.bracket_colors[self.brackets % c.bracket_colors.len()],
|
||||
s,
|
||||
)
|
||||
}
|
||||
pub fn go_deeper(&mut self) {
|
||||
self.depth += 1;
|
||||
}
|
||||
pub fn go_shallower(&mut self) {
|
||||
self.depth -= 1;
|
||||
}
|
||||
pub fn variable_ref_symbol(&self, _info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
s
|
||||
}
|
||||
pub fn variable(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.variable_color, s)
|
||||
}
|
||||
pub fn if_if(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_if_color, s)
|
||||
}
|
||||
pub fn if_else(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_else_color, s)
|
||||
}
|
||||
pub fn loop_loop(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_loop_color, s)
|
||||
}
|
||||
pub fn loop_for(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_for_color, s)
|
||||
}
|
||||
pub fn kw_switch(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_switch_color, s)
|
||||
}
|
||||
pub fn kw_match(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.keyword_match_color, s)
|
||||
}
|
||||
pub fn fncall(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_call_color, s)
|
||||
}
|
||||
pub fn fndef_fn(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_def_fn_color, s)
|
||||
}
|
||||
pub fn fndef_name(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.function_def_name_color, s)
|
||||
}
|
||||
pub fn value_string_quotes(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.value_string_quotes_color, s)
|
||||
}
|
||||
pub fn value_string_content(&self, info: Option<&GlobalScriptInfo>, s: String) -> String {
|
||||
self.color(info, |c| &c.value_string_content_color, s)
|
||||
}
|
||||
pub fn line_prefix(&self) -> String {
|
||||
" ".repeat(self.depth)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FormatGs {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result;
|
||||
fn with_info<'a>(&'a self, info: &'a GlobalScriptInfo) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: Some(info),
|
||||
file: None,
|
||||
}
|
||||
}
|
||||
fn with_file<'a>(&'a self, file: &'a crate::parsing::file::File) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: None,
|
||||
file: Some(file),
|
||||
}
|
||||
}
|
||||
fn with_info_and_file<'a>(
|
||||
&'a self,
|
||||
info: &'a GlobalScriptInfo,
|
||||
file: &'a crate::parsing::file::File,
|
||||
) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info: Some(info),
|
||||
file: Some(file),
|
||||
}
|
||||
}
|
||||
fn with<'a>(
|
||||
&'a self,
|
||||
info: Option<&'a GlobalScriptInfo>,
|
||||
file: Option<&'a crate::parsing::file::File>,
|
||||
) -> FormatWithGs<'a, Self> {
|
||||
FormatWithGs {
|
||||
format: &self,
|
||||
info,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct FormatWithGs<'a, T: ?Sized>
|
||||
where
|
||||
T: FormatGs,
|
||||
{
|
||||
format: &'a T,
|
||||
info: Option<&'a GlobalScriptInfo>,
|
||||
file: Option<&'a crate::parsing::file::File>,
|
||||
}
|
||||
impl<'a, T> Display for FormatWithGs<'a, T>
|
||||
where
|
||||
T: FormatGs,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.format
|
||||
.fmtgs(f, self.info, &mut FormatInfo::default(), self.file.clone())
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins,
|
||||
fmtgs::Color,
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
pub type GSInfo = Arc<GlobalScriptInfo>;
|
||||
|
||||
pub struct GlobalScriptInfo {
|
||||
pub libs: Vec<crate::libs::Lib>,
|
||||
|
||||
pub main_fn_args: Vec<(String, VType)>,
|
||||
|
||||
pub lib_fns: HashMap<String, (usize, usize)>,
|
||||
|
||||
pub enum_variants: HashMap<String, usize>,
|
||||
|
||||
pub custom_type_names: HashMap<String, usize>,
|
||||
pub custom_types: Vec<VType>,
|
||||
|
||||
/// if true, trying to assign to the reference of a variable that doesn't exist yet will create and initialize that variable.
|
||||
/// if false, variables will only be initialized if this is explicitly stated.
|
||||
/// settings this to true is useful for "x = 2; x = 5;" syntax in parser implementations that don't differenciate initialization and assignment syntactically.
|
||||
pub to_runnable_automatic_initialization: bool,
|
||||
|
||||
pub formatter: ColorFormatter,
|
||||
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
pub struct ColorFormatter {
|
||||
pub mode: ColorFormatMode,
|
||||
pub bracket_colors: Vec<Color>,
|
||||
pub value_string_quotes_color: Color,
|
||||
pub value_string_content_color: Color,
|
||||
pub keyword_if_color: Color,
|
||||
pub keyword_else_color: Color,
|
||||
pub keyword_loop_color: Color,
|
||||
pub keyword_for_color: Color,
|
||||
pub keyword_switch_color: Color,
|
||||
pub keyword_match_color: Color,
|
||||
pub function_call_color: Color,
|
||||
pub function_def_fn_color: Color,
|
||||
pub function_def_name_color: Color,
|
||||
pub variable_color: Color,
|
||||
}
|
||||
impl Default for ColorFormatter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: ColorFormatMode::Plain,
|
||||
bracket_colors: vec![
|
||||
Color::Red,
|
||||
Color::Yellow,
|
||||
Color::Cyan,
|
||||
Color::Blue,
|
||||
Color::Magenta,
|
||||
],
|
||||
value_string_quotes_color: Color::Grey,
|
||||
value_string_content_color: Color::Cyan,
|
||||
keyword_if_color: Color::Yellow,
|
||||
keyword_else_color: Color::Yellow,
|
||||
keyword_loop_color: Color::Yellow,
|
||||
keyword_for_color: Color::Yellow,
|
||||
keyword_switch_color: Color::Yellow,
|
||||
keyword_match_color: Color::Yellow,
|
||||
function_call_color: Color::Magenta,
|
||||
function_def_fn_color: Color::Blue,
|
||||
function_def_name_color: Color::Magenta,
|
||||
variable_color: Color::Green,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ColorFormatMode {
|
||||
/// No color.
|
||||
Plain,
|
||||
/// For terminal output
|
||||
Colorize,
|
||||
}
|
||||
|
||||
impl GlobalScriptInfo {
|
||||
pub fn to_arc(self) -> GSInfo {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GlobalScriptInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
libs: vec![],
|
||||
lib_fns: HashMap::new(),
|
||||
main_fn_args: vec![],
|
||||
enum_variants: Self::default_enum_variants(),
|
||||
custom_type_names: HashMap::new(),
|
||||
custom_types: vec![],
|
||||
to_runnable_automatic_initialization: false,
|
||||
formatter: Default::default(),
|
||||
log: Logger::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl GlobalScriptInfo {
|
||||
pub fn default_enum_variants() -> HashMap<String, usize> {
|
||||
builtins::EVS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (v.to_string(), i))
|
||||
.collect()
|
||||
}
|
||||
pub fn set_main_fn_args(&mut self, args: Vec<(String, VType)>) {
|
||||
self.main_fn_args = args;
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_enum_variant(&mut self, name: String) -> usize {
|
||||
let id = self.enum_variants.len();
|
||||
self.enum_variants.insert(name, id);
|
||||
id
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_custom_type(&mut self, name: String, t: VType) -> usize {
|
||||
let id = self.custom_types.len();
|
||||
self.custom_types.push(t);
|
||||
self.custom_type_names.insert(name, id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Logger {
|
||||
logs: Arc<Mutex<Vec<LogMsg>>>,
|
||||
|
||||
pub after_parse: LogKind,
|
||||
|
||||
pub vtype_fits_in: LogKind,
|
||||
pub vsingletype_fits_in: LogKind,
|
||||
}
|
||||
impl Logger {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
logs: Arc::new(Mutex::new(vec![])),
|
||||
after_parse: Default::default(),
|
||||
vtype_fits_in: Default::default(),
|
||||
vsingletype_fits_in: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogMsg {
|
||||
AfterParse(String),
|
||||
VTypeFitsIn(VType, VType, Vec<VSingleType>),
|
||||
VSingleTypeFitsIn(VSingleType, VSingleType, bool),
|
||||
}
|
||||
impl Logger {
|
||||
pub fn log(&self, msg: LogMsg) {
|
||||
let kind = match msg {
|
||||
LogMsg::AfterParse(..) => &self.after_parse,
|
||||
LogMsg::VTypeFitsIn(..) => &self.vtype_fits_in,
|
||||
LogMsg::VSingleTypeFitsIn(..) => &self.vsingletype_fits_in,
|
||||
};
|
||||
if kind.stderr {
|
||||
eprintln!("{msg}");
|
||||
}
|
||||
if kind.log {
|
||||
if let Ok(mut logs) = self.logs.lock() {
|
||||
logs.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for LogMsg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AfterParse(code) => {
|
||||
write!(f, "AfterParse :: {code}")
|
||||
}
|
||||
Self::VTypeFitsIn(a, b, no) => write!(f, "VTypeFitsIn :: {a} in {b} ? -> {no:?}"),
|
||||
Self::VSingleTypeFitsIn(a, b, fits) => {
|
||||
write!(f, "VSingleTypeFitsIn :: {a} in {b} ? -> {fits}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LogKind {
|
||||
pub stderr: bool,
|
||||
pub log: bool,
|
||||
}
|
||||
impl LogKind {
|
||||
pub fn log(&self) -> bool {
|
||||
self.stderr || self.log
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
pub mod builtins;
|
||||
pub mod code_macro;
|
||||
pub mod code_parsed;
|
||||
pub mod code_runnable;
|
||||
pub mod fmtgs;
|
||||
pub mod global_info;
|
||||
pub mod to_runnable;
|
||||
pub mod val_data;
|
||||
pub mod val_type;
|
||||
@@ -1,749 +0,0 @@
|
||||
use core::panic;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Display},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::lang::{
|
||||
global_info::GlobalScriptInfo,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
use super::{
|
||||
builtins::BuiltinFunction,
|
||||
code_macro::Macro,
|
||||
code_parsed::{SBlock, SFunction, SStatement, SStatementEnum},
|
||||
code_runnable::{RBlock, RFunction, RFunctionType, RScript, RStatement, RStatementEnum},
|
||||
fmtgs::FormatGs,
|
||||
global_info::GSInfo,
|
||||
};
|
||||
|
||||
pub enum ToRunnableError {
|
||||
UseOfUndefinedVariable(String),
|
||||
UseOfUndefinedFunction(String),
|
||||
UnknownType(String),
|
||||
CannotDereferenceTypeNTimes(VType, usize, VType),
|
||||
FunctionWrongArgs(String, Vec<Arc<RFunction>>, Vec<VType>),
|
||||
InvalidType {
|
||||
expected: VType,
|
||||
found: VType,
|
||||
problematic: VType,
|
||||
},
|
||||
CannotAssignTo(VType, VType),
|
||||
CaseForceButTypeNotCovered(VType),
|
||||
MatchConditionInvalidReturn(VType),
|
||||
NotIndexableFixed(VType, usize),
|
||||
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
|
||||
WrongArgsForLibFunction(String, Vec<VType>),
|
||||
ForLoopContainerHasNoInnerTypes,
|
||||
StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType),
|
||||
}
|
||||
impl Debug for ToRunnableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// - Don't use {} to format, use .fmtgs(f, info, form, file) instead!
|
||||
// - Show location in code where the error was found
|
||||
impl Display for ToRunnableError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
impl FormatGs for ToRunnableError {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UseOfUndefinedVariable(v) => {
|
||||
write!(f, "Cannot use variable \"{v}\" as it isn't defined (yet?).")
|
||||
}
|
||||
Self::UseOfUndefinedFunction(v) => {
|
||||
write!(f, "Cannot use function \"{v}\" as it isn't defined (yet?).")
|
||||
}
|
||||
Self::UnknownType(name) => write!(f, "Unknown type \"{name}\"."),
|
||||
Self::CannotDereferenceTypeNTimes(og_type, derefs_wanted, last_valid_type) => {
|
||||
write!(f, "Cannot dereference type ")?;
|
||||
og_type.fmtgs(f, info, form, file)?;
|
||||
write!(f, " {derefs_wanted} times (stopped at ")?;
|
||||
last_valid_type.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::FunctionWrongArgs(fn_name, possible_fns, given_types) => {
|
||||
write!(f, "Wrong args for function \"{fn_name}\": Found (")?;
|
||||
for (i, t) in given_types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "), but valid are only ")?;
|
||||
for (i, func) in possible_fns.iter().enumerate() {
|
||||
if i != 0 {
|
||||
if i + 1 == possible_fns.len() {
|
||||
write!(f, ", and ")?;
|
||||
} else {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(Arc::clone(func)).fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::InvalidType {
|
||||
expected,
|
||||
found,
|
||||
problematic,
|
||||
} => {
|
||||
write!(f, "Invalid type: Expected ")?;
|
||||
expected.fmtgs(f, info, form, file)?;
|
||||
write!(f, " but found ")?;
|
||||
found.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", which includes ")?;
|
||||
problematic.fmtgs(f, info, form, file)?;
|
||||
write!(f, " which is not covered.")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::CaseForceButTypeNotCovered(v) => {
|
||||
write!(
|
||||
f,
|
||||
"Switch! statement, but not all types covered. Types to cover: "
|
||||
)?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
Ok(())
|
||||
}
|
||||
Self::MatchConditionInvalidReturn(v) => {
|
||||
write!(f, "match statement condition returned ")?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", which is not necessarily a tuple of size 0 to 1.")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::NotIndexableFixed(t, i) => {
|
||||
write!(f, "Cannot use fixed-index {i} on type ")?;
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::WrongInputsForBuiltinFunction(_builtin, builtin_name, args) => {
|
||||
write!(
|
||||
f,
|
||||
"Wrong arguments for builtin function \"{}\":",
|
||||
builtin_name
|
||||
)?;
|
||||
for arg in args {
|
||||
write!(f, " ")?;
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")
|
||||
}
|
||||
Self::WrongArgsForLibFunction(name, args) => {
|
||||
write!(f, "Wrong arguments for library function {}:", name)?;
|
||||
for arg in args {
|
||||
write!(f, " ")?;
|
||||
arg.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ".")
|
||||
}
|
||||
Self::CannotAssignTo(val, target) => {
|
||||
write!(f, "Cannot assign type ")?;
|
||||
val.fmtgs(f, info, form, file)?;
|
||||
write!(f, " to ")?;
|
||||
target.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
Self::ForLoopContainerHasNoInnerTypes => {
|
||||
write!(f, "For loop: container had no inner types, cannot iterate.")
|
||||
}
|
||||
Self::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(
|
||||
required,
|
||||
real,
|
||||
problematic,
|
||||
) => {
|
||||
write!(f, "the statement requires its output type to be ")?;
|
||||
required.fmtgs(f, info, form, file)?;
|
||||
write!(f, ", but its real output type is ")?;
|
||||
real.fmtgs(f, info, form, file)?;
|
||||
write!(
|
||||
f,
|
||||
", which doesn't fit in the required type because of the problematic types "
|
||||
)?;
|
||||
problematic.fmtgs(f, info, form, file)?;
|
||||
write!(f, ".")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local, used to keep local variables separated
|
||||
#[derive(Clone)]
|
||||
struct LInfo {
|
||||
vars: HashMap<String, Arc<Mutex<(VData, VType)>>>,
|
||||
fns: HashMap<String, Vec<Arc<RFunction>>>,
|
||||
}
|
||||
|
||||
pub fn to_runnable(
|
||||
s: SFunction,
|
||||
mut ginfo: GlobalScriptInfo,
|
||||
) -> Result<RScript, (ToRunnableError, GSInfo)> {
|
||||
let func = match function(
|
||||
&s,
|
||||
&mut ginfo,
|
||||
LInfo {
|
||||
vars: HashMap::new(),
|
||||
fns: HashMap::new(),
|
||||
},
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((e, ginfo.to_arc())),
|
||||
};
|
||||
let ginfo = ginfo.to_arc();
|
||||
match RScript::new(func, ginfo.clone()) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err((e, ginfo)),
|
||||
}
|
||||
}
|
||||
|
||||
fn function(
|
||||
s: &SFunction,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
mut linfo: LInfo,
|
||||
) -> Result<RFunction, ToRunnableError> {
|
||||
let mut input_vars = vec![];
|
||||
let mut input_types = vec![];
|
||||
for (iname, itype) in &s.inputs {
|
||||
let mut itype = itype.to_owned();
|
||||
stypes(&mut itype, ginfo)?;
|
||||
let var = Arc::new(Mutex::new((VData::new_placeholder(), itype.clone())));
|
||||
linfo.vars.insert(iname.clone(), Arc::clone(&var));
|
||||
input_vars.push(var);
|
||||
input_types.push(itype);
|
||||
}
|
||||
// set the types to all possible types (get_all_functions sets the types to one single type to get the return type of the block for that case)
|
||||
for (varid, vartype) in s.inputs.iter().zip(input_types.iter()) {
|
||||
linfo.vars.get(&varid.0).unwrap().lock().unwrap().1 = vartype.clone();
|
||||
}
|
||||
let mut o = RFunction {
|
||||
out_map: vec![],
|
||||
statement: RFunctionType::Statement(
|
||||
input_vars,
|
||||
statement(&s.statement, ginfo, &mut linfo.clone())?,
|
||||
input_types,
|
||||
),
|
||||
};
|
||||
o.out_map = {
|
||||
let mut map = vec![];
|
||||
let mut indices: Vec<_> = if let RFunctionType::Statement(_, _, input_types) = &o.statement
|
||||
{
|
||||
input_types.iter().map(|_| 0).collect()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
// like counting: advance first index, when we reach the end, reset to zero and advance the next index, ...
|
||||
loop {
|
||||
if let RFunctionType::Statement(_, _, input_types) = &o.statement {
|
||||
let mut current_types = Vec::with_capacity(input_types.len());
|
||||
let mut adv = true;
|
||||
let mut was_last = input_types.is_empty();
|
||||
for i in 0..input_types.len() {
|
||||
current_types.push(match input_types[i].types.get(indices[i]) {
|
||||
Some(v) => v.clone().to(),
|
||||
None => VType::empty(),
|
||||
});
|
||||
if adv {
|
||||
if indices[i] + 1 < input_types[i].types.len() {
|
||||
indices[i] += 1;
|
||||
adv = false;
|
||||
} else {
|
||||
indices[i] = 0;
|
||||
// we just reset the last index back to 0 - if we don't break
|
||||
// from the loop, we will just start all over again.
|
||||
if i + 1 == input_types.len() {
|
||||
was_last = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = o
|
||||
.out_by_statement(¤t_types, &ginfo)
|
||||
.expect("this should always be a Statement function type");
|
||||
map.push((current_types, out));
|
||||
if was_last {
|
||||
break map;
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(o)
|
||||
}
|
||||
|
||||
fn block(
|
||||
s: &SBlock,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
mut linfo: LInfo,
|
||||
) -> Result<RBlock, ToRunnableError> {
|
||||
let mut statements = Vec::new();
|
||||
for st in &s.statements {
|
||||
statements.push(statement(st, ginfo, &mut linfo)?);
|
||||
}
|
||||
Ok(RBlock { statements })
|
||||
}
|
||||
|
||||
pub fn stypes(t: &mut VType, ginfo: &mut GlobalScriptInfo) -> Result<(), ToRunnableError> {
|
||||
for t in &mut t.types {
|
||||
stype(t, ginfo)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn stype(t: &mut VSingleType, ginfo: &mut GlobalScriptInfo) -> Result<(), ToRunnableError> {
|
||||
match t {
|
||||
VSingleType::Any
|
||||
| VSingleType::Bool
|
||||
| VSingleType::Int
|
||||
| VSingleType::Float
|
||||
| VSingleType::String => (),
|
||||
VSingleType::Tuple(v) => {
|
||||
for t in v {
|
||||
stypes(t, ginfo)?;
|
||||
}
|
||||
}
|
||||
VSingleType::List(t) => stypes(t, ginfo)?,
|
||||
VSingleType::Reference(t) => stypes(t, ginfo)?,
|
||||
VSingleType::Thread(t) => stypes(t, ginfo)?,
|
||||
VSingleType::EnumVariantS(e, v) => {
|
||||
*t = VSingleType::EnumVariant(
|
||||
{
|
||||
if let Some(v) = ginfo.enum_variants.get(e) {
|
||||
*v
|
||||
} else {
|
||||
let v = ginfo.enum_variants.len();
|
||||
ginfo.enum_variants.insert(e.clone(), v);
|
||||
v
|
||||
}
|
||||
},
|
||||
{
|
||||
stypes(v, ginfo)?;
|
||||
v.clone()
|
||||
},
|
||||
)
|
||||
}
|
||||
VSingleType::Function(io_map) => {
|
||||
for io_variant in io_map {
|
||||
for i in &mut io_variant.0 {
|
||||
stypes(i, ginfo)?;
|
||||
}
|
||||
stypes(&mut io_variant.1, ginfo)?;
|
||||
}
|
||||
}
|
||||
VSingleType::EnumVariant(_, t) => stypes(t, ginfo)?,
|
||||
VSingleType::CustomTypeS(name) => {
|
||||
*t = VSingleType::CustomType(
|
||||
if let Some(v) = ginfo.custom_type_names.get(&name.to_lowercase()) {
|
||||
*v
|
||||
} else {
|
||||
return Err(ToRunnableError::UnknownType(name.to_owned()));
|
||||
},
|
||||
)
|
||||
}
|
||||
VSingleType::CustomType(_) => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn statement(
|
||||
s: &SStatement,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
linfo: &mut LInfo,
|
||||
) -> Result<RStatement, ToRunnableError> {
|
||||
statement_adv(s, ginfo, linfo, &mut None)
|
||||
}
|
||||
fn statement_adv(
|
||||
s: &SStatement,
|
||||
ginfo: &mut GlobalScriptInfo,
|
||||
linfo: &mut LInfo,
|
||||
// if Some((t, is_init)), the statement creates by this function is the left side of an assignment, meaning it can create variables. t is the type that will be assigned to it.
|
||||
to_be_assigned_to: &mut Option<(VType, &mut bool)>,
|
||||
) -> Result<RStatement, ToRunnableError> {
|
||||
// eprintln!("TR : {}", s);
|
||||
// if let Some(t) = &to_be_assigned_to {
|
||||
// eprintln!(" --> {}", t.0);
|
||||
// }
|
||||
let mut state = match &*s.statement {
|
||||
SStatementEnum::Value(v) => RStatementEnum::Value(v.clone()).to(),
|
||||
SStatementEnum::Tuple(v) | SStatementEnum::List(v) => {
|
||||
let mut w = Vec::with_capacity(v.len());
|
||||
let mut prev = None;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if let Some(t) = to_be_assigned_to {
|
||||
let out_t = if let Some(p) = &prev { p } else { &t.0 };
|
||||
let inner_t = if let Some(v) = out_t.get_always(i, ginfo) {
|
||||
v
|
||||
} else {
|
||||
panic!("cannot assign: cannot get_always({i}) on type {}.", out_t);
|
||||
};
|
||||
let p = std::mem::replace(&mut t.0, inner_t);
|
||||
if prev.is_none() {
|
||||
prev = Some(p);
|
||||
}
|
||||
};
|
||||
w.push(statement_adv(v, ginfo, linfo, to_be_assigned_to)?);
|
||||
}
|
||||
if let (Some(t), Some(prev)) = (to_be_assigned_to, prev) {
|
||||
t.0 = prev;
|
||||
}
|
||||
if let SStatementEnum::List(_) = &*s.statement {
|
||||
RStatementEnum::List(w)
|
||||
} else {
|
||||
RStatementEnum::Tuple(w)
|
||||
}
|
||||
.to()
|
||||
}
|
||||
SStatementEnum::Variable(v, is_ref) => {
|
||||
let existing_var = linfo.vars.get(v);
|
||||
let is_init_force = if let Some(v) = &to_be_assigned_to {
|
||||
*v.1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// we can't assign to a variable that doesn't exist yet -> create a new one
|
||||
if is_init_force
|
||||
|| (existing_var.is_none() && ginfo.to_runnable_automatic_initialization)
|
||||
{
|
||||
// if to_be_assigned_to is some (-> this is on the left side of an assignment), create a new variable. else, return an error.
|
||||
if let Some((t, is_init)) = to_be_assigned_to {
|
||||
**is_init = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let var = VData::new_placeholder();
|
||||
#[cfg(debug_assertions)]
|
||||
let var = VData::new_placeholder_with_name(v.to_owned());
|
||||
let var_arc = Arc::new(Mutex::new((var, t.clone())));
|
||||
linfo.vars.insert(v.to_owned(), Arc::clone(&var_arc));
|
||||
RStatementEnum::Variable(var_arc, true)
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||
}
|
||||
} else if let Some(var) = existing_var {
|
||||
RStatementEnum::Variable(Arc::clone(&var), *is_ref)
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||
}
|
||||
.to()
|
||||
}
|
||||
SStatementEnum::FunctionCall(v, args) => {
|
||||
let mut rargs = Vec::with_capacity(args.len());
|
||||
for arg in args.iter() {
|
||||
rargs.push(statement(arg, ginfo, linfo)?);
|
||||
}
|
||||
let arg_types: Vec<_> = rargs.iter().map(|v| v.out(ginfo)).collect();
|
||||
fn check_fn_args(
|
||||
arg_types: &Vec<VType>,
|
||||
func: &RFunction,
|
||||
ginfo: &GlobalScriptInfo,
|
||||
) -> bool {
|
||||
func.out_by_map(arg_types, ginfo).is_some()
|
||||
// func.inputs.len() == arg_types.len()
|
||||
// && func
|
||||
// .inputs
|
||||
// .iter()
|
||||
// .zip(arg_types.iter())
|
||||
// .all(|(fn_in, arg)| arg.fits_in(&fn_in.lock().unwrap().1, ginfo).is_empty())
|
||||
}
|
||||
if let Some(funcs) = linfo.fns.get(v) {
|
||||
'find_func: {
|
||||
for func in funcs.iter().rev() {
|
||||
if check_fn_args(&arg_types, &func, ginfo) {
|
||||
break 'find_func RStatementEnum::FunctionCall(
|
||||
Arc::clone(&func),
|
||||
rargs,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Err(ToRunnableError::FunctionWrongArgs(
|
||||
v.to_owned(),
|
||||
funcs.iter().map(|v| Arc::clone(v)).collect(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if let Some(builtin) = BuiltinFunction::get(v) {
|
||||
let arg_types = rargs.iter().map(|v| v.out(ginfo)).collect();
|
||||
if builtin.can_take(&arg_types, ginfo) {
|
||||
RStatementEnum::BuiltinFunctionCall(builtin, rargs)
|
||||
} else {
|
||||
return Err(ToRunnableError::WrongInputsForBuiltinFunction(
|
||||
builtin,
|
||||
v.to_string(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// LIBRARY FUNCTION?
|
||||
if let Some((libid, fnid)) = ginfo.lib_fns.get(v) {
|
||||
let lib = &ginfo.libs[*libid];
|
||||
let libfn = &lib.registered_fns[*fnid];
|
||||
let mut empty = true;
|
||||
let fn_out =
|
||||
libfn
|
||||
.1
|
||||
.iter()
|
||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||
if fn_in.len() == arg_types.len()
|
||||
&& fn_in.iter().zip(arg_types.iter()).all(|(fn_in, arg)| {
|
||||
arg.fits_in(fn_in, ginfo).is_empty()
|
||||
})
|
||||
{
|
||||
empty = false;
|
||||
t.add_typesr(fn_out, ginfo);
|
||||
}
|
||||
t
|
||||
});
|
||||
if empty {
|
||||
return Err(ToRunnableError::WrongArgsForLibFunction(
|
||||
v.to_owned(),
|
||||
arg_types,
|
||||
));
|
||||
}
|
||||
RStatementEnum::LibFunctionCall(*libid, *fnid, rargs, fn_out.clone())
|
||||
} else {
|
||||
return Err(ToRunnableError::UseOfUndefinedFunction(v.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.to(),
|
||||
SStatementEnum::FunctionDefinition(name, f) => {
|
||||
let f = Arc::new(function(f, ginfo, linfo.clone())?);
|
||||
if let Some(name) = name {
|
||||
// named function => add to global functions
|
||||
let f = Arc::clone(&f);
|
||||
if let Some(vec) = linfo.fns.get_mut(name) {
|
||||
vec.push(f);
|
||||
} else {
|
||||
linfo.fns.insert(name.clone(), vec![f]);
|
||||
}
|
||||
}
|
||||
RStatementEnum::Value(VDataEnum::Function(f).to()).to()
|
||||
}
|
||||
SStatementEnum::Block(b) => RStatementEnum::Block(block(&b, ginfo, linfo.clone())?).to(),
|
||||
SStatementEnum::If(c, t, e) => RStatementEnum::If(
|
||||
{
|
||||
let condition = statement(&c, ginfo, linfo)?;
|
||||
let out = condition.out(ginfo).fits_in(
|
||||
&VType {
|
||||
types: vec![VSingleType::Bool],
|
||||
},
|
||||
ginfo,
|
||||
);
|
||||
if out.is_empty() {
|
||||
condition
|
||||
} else {
|
||||
return Err(ToRunnableError::InvalidType {
|
||||
expected: VSingleType::Bool.into(),
|
||||
found: condition.out(ginfo),
|
||||
problematic: VType { types: out },
|
||||
});
|
||||
}
|
||||
},
|
||||
statement(&t, ginfo, linfo)?,
|
||||
match e {
|
||||
Some(v) => Some(statement(&v, ginfo, linfo)?),
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
.to(),
|
||||
SStatementEnum::Loop(c) => RStatementEnum::Loop(statement(&c, ginfo, linfo)?).to(),
|
||||
SStatementEnum::For(v, c, b) => {
|
||||
let mut linfo = linfo.clone();
|
||||
let container = statement(&c, ginfo, &mut linfo)?;
|
||||
let inner = container.out(ginfo).inner_types_for_iters(ginfo);
|
||||
if inner.types.is_empty() {
|
||||
return Err(ToRunnableError::ForLoopContainerHasNoInnerTypes);
|
||||
}
|
||||
let assign_to = statement_adv(v, ginfo, &mut linfo, &mut Some((inner, &mut true)))?;
|
||||
let block = statement(&b, ginfo, &mut linfo)?;
|
||||
let o = RStatementEnum::For(assign_to, container, block);
|
||||
o.to()
|
||||
}
|
||||
|
||||
SStatementEnum::Switch(switch_on, cases, force) => {
|
||||
let mut ncases = Vec::with_capacity(cases.len());
|
||||
let switch_on = statement(switch_on, ginfo, linfo)?;
|
||||
let og_type = switch_on.out(ginfo);
|
||||
let mut covered_types = VType::empty();
|
||||
for (case_type, case_assign_to, case_action) in cases.iter() {
|
||||
let mut linfo = linfo.clone();
|
||||
let case_type = {
|
||||
let mut v = case_type.clone();
|
||||
stypes(&mut v, ginfo)?;
|
||||
v
|
||||
};
|
||||
covered_types.add_typesr(&case_type, ginfo);
|
||||
ncases.push((
|
||||
case_type.clone(),
|
||||
statement_adv(
|
||||
case_assign_to,
|
||||
ginfo,
|
||||
&mut linfo,
|
||||
&mut Some((case_type, &mut true)),
|
||||
)?,
|
||||
statement(case_action, ginfo, &mut linfo)?,
|
||||
));
|
||||
}
|
||||
if *force {
|
||||
let types_not_covered = og_type.fits_in(&covered_types, ginfo);
|
||||
if !types_not_covered.is_empty() {
|
||||
return Err(ToRunnableError::CaseForceButTypeNotCovered(VType {
|
||||
types: types_not_covered,
|
||||
}));
|
||||
}
|
||||
}
|
||||
RStatementEnum::Switch(switch_on, ncases, *force).to()
|
||||
}
|
||||
SStatementEnum::Match(cases) => {
|
||||
let _ncases: Vec<(RStatement, RStatement, RStatement)> =
|
||||
Vec::with_capacity(cases.len());
|
||||
let mut ncases = Vec::with_capacity(cases.len());
|
||||
let mut out_type = VType::empty();
|
||||
let may_not_match = true;
|
||||
for (condition, assign_to, action) in cases.iter() {
|
||||
let mut linfo = linfo.clone();
|
||||
let condition = statement(condition, ginfo, &mut linfo)?;
|
||||
let (can_fail, matches) = condition.out(ginfo).matches(ginfo);
|
||||
let assign_to = statement_adv(
|
||||
assign_to,
|
||||
ginfo,
|
||||
&mut linfo,
|
||||
&mut Some((matches, &mut true)),
|
||||
)?;
|
||||
let action = statement(action, ginfo, &mut linfo)?;
|
||||
ncases.push((condition, assign_to, action));
|
||||
if !can_fail {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if may_not_match {
|
||||
out_type.add_type(VSingleType::Tuple(vec![]), ginfo);
|
||||
}
|
||||
|
||||
RStatementEnum::Match(ncases).to()
|
||||
}
|
||||
|
||||
SStatementEnum::IndexFixed(st, i) => {
|
||||
let st = statement(st, ginfo, linfo)?;
|
||||
if st.out(ginfo).get_always(*i, ginfo).is_some() {
|
||||
RStatementEnum::IndexFixed(st, *i).to()
|
||||
} else {
|
||||
return Err(ToRunnableError::NotIndexableFixed(st.out(ginfo), *i));
|
||||
}
|
||||
}
|
||||
SStatementEnum::EnumVariant(variant, s) => RStatementEnum::EnumVariant(
|
||||
{
|
||||
if let Some(v) = ginfo.enum_variants.get(variant) {
|
||||
*v
|
||||
} else {
|
||||
let v = ginfo.enum_variants.len();
|
||||
ginfo.enum_variants.insert(variant.clone(), v);
|
||||
v
|
||||
}
|
||||
},
|
||||
statement(s, ginfo, linfo)?,
|
||||
)
|
||||
.to(),
|
||||
SStatementEnum::TypeDefinition(name, t) => {
|
||||
// insert to name map has to happen before stypes()
|
||||
ginfo
|
||||
.custom_type_names
|
||||
.insert(name.to_lowercase(), ginfo.custom_types.len());
|
||||
let mut t = t.to_owned();
|
||||
stypes(&mut t, ginfo)?;
|
||||
ginfo.custom_types.push(t);
|
||||
RStatementEnum::Value(VDataEnum::Tuple(vec![]).to()).to()
|
||||
}
|
||||
SStatementEnum::Macro(m) => match m {
|
||||
Macro::StaticMers(val) => RStatementEnum::Value(val.clone()).to(),
|
||||
},
|
||||
};
|
||||
state.derefs = s.derefs;
|
||||
// if force_output_type is set, verify that the real output type actually fits in the forced one.
|
||||
if let Some(force_opt) = &s.force_output_type {
|
||||
let mut force_opt = force_opt.to_owned();
|
||||
stypes(&mut force_opt, ginfo)?;
|
||||
let real_output_type = state.out(ginfo);
|
||||
let problematic_types = real_output_type.fits_in(&force_opt, ginfo);
|
||||
if problematic_types.is_empty() {
|
||||
state.force_output_type = Some(force_opt);
|
||||
} else {
|
||||
return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types }));
|
||||
}
|
||||
}
|
||||
if let Some((opt, is_init)) = &s.output_to {
|
||||
// if false, may be changed to true by statement_adv
|
||||
let mut is_init = *is_init;
|
||||
let optr = statement_adv(
|
||||
opt,
|
||||
ginfo,
|
||||
linfo,
|
||||
&mut Some((state.out(ginfo), &mut is_init)),
|
||||
)?;
|
||||
state.output_to = Some((Box::new(optr), is_init));
|
||||
//
|
||||
// if let Some((var_id, var_out)) = linfo.vars.get(opt) {
|
||||
// let out = state.out(ginfo);
|
||||
// let mut var_derefd = var_out.clone();
|
||||
// for _ in 0..*derefs {
|
||||
// var_derefd = if let Some(v) = var_derefd.dereference() {
|
||||
// v
|
||||
// } else {
|
||||
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
|
||||
// var_out.clone(),
|
||||
// *derefs,
|
||||
// var_derefd,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// let inv_types = out.fits_in(&var_derefd, ginfo);
|
||||
// if !inv_types.is_empty() {
|
||||
// eprintln!("Warn: shadowing variable {opt} because statement's output type {out} does not fit in the original variable's {var_out}. This might become an error in the future, or it might stop shadowing the variiable entirely - for stable scripts, avoid this by giving the variable a different name.");
|
||||
// if *derefs != 0 {
|
||||
// return Err(ToRunnableError::CannotDeclareVariableWithDereference(
|
||||
// opt.clone(),
|
||||
// ));
|
||||
// }
|
||||
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
|
||||
// state.output_to = Some((ginfo.vars, 0, true));
|
||||
// ginfo.vars += 1;
|
||||
// } else {
|
||||
// // mutate existing variable
|
||||
// state.output_to = Some((*var_id, *derefs, false));
|
||||
// }
|
||||
// } else {
|
||||
// let mut out = state.out(ginfo);
|
||||
// for _ in 0..*derefs {
|
||||
// out = if let Some(v) = out.dereference() {
|
||||
// v
|
||||
// } else {
|
||||
// return Err(ToRunnableError::CannotDereferenceTypeNTimes(
|
||||
// state.out(ginfo),
|
||||
// *derefs,
|
||||
// out,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// linfo.vars.insert(opt.clone(), (ginfo.vars, out));
|
||||
// state.output_to = Some((ginfo.vars, *derefs, true));
|
||||
// ginfo.vars += 1;
|
||||
// }
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
@@ -1,586 +0,0 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{
|
||||
code_runnable::RFunction,
|
||||
fmtgs::FormatGs,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VDataEnum {
|
||||
Bool(bool),
|
||||
Int(isize),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Tuple(Vec<VData>),
|
||||
List(VType, Vec<VData>),
|
||||
Function(Arc<RFunction>),
|
||||
Thread(thread::VDataThread, VType),
|
||||
Reference(VData),
|
||||
EnumVariant(usize, Box<VData>),
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub struct VData(Arc<Mutex<VDataInner>>);
|
||||
#[cfg(debug_assertions)]
|
||||
pub struct VData(pub Arc<Mutex<VDataInner>>, pub Option<String>);
|
||||
pub enum VDataInner {
|
||||
Data(usize, Box<VDataEnum>),
|
||||
Mut(Arc<Mutex<VData>>),
|
||||
ClonedFrom(VData),
|
||||
}
|
||||
/// can be either Data, Mut or ClonedFrom.
|
||||
/// - any ClonedFrom will point to a Data variant. It can never point to anything else.
|
||||
/// it will increase the Data's clone count by one on creation and decrease it again on Drop::drop().
|
||||
/// - any Mut will eventually point to a ClonedFrom or a Data variant. It can also point to another Mut.
|
||||
impl VDataInner {
|
||||
fn to(self) -> VData {
|
||||
#[cfg(not(debug_assertions))]
|
||||
return VData(Arc::new(Mutex::new(self)));
|
||||
#[cfg(debug_assertions)]
|
||||
return VData(Arc::new(Mutex::new(self)), None);
|
||||
}
|
||||
}
|
||||
impl VDataEnum {
|
||||
pub fn to(self) -> VData {
|
||||
VDataInner::Data(0, Box::new(self)).to()
|
||||
}
|
||||
}
|
||||
|
||||
impl VData {
|
||||
pub fn new_placeholder() -> Self {
|
||||
VDataEnum::Bool(false).to()
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn new_placeholder_with_name(name: String) -> Self {
|
||||
let mut o = VDataEnum::Bool(false).to();
|
||||
o.1 = Some(name);
|
||||
o
|
||||
}
|
||||
/// clones self, retrurning a new instance of self that will always yield the value self had when this function was called.
|
||||
/// note to dev: since the actual data is stored in VDataEnum, which either clones data or calls clone() (= clone_data()) on further VData, this automatically borrows all child data as immutable too. rust's Drop::drop() implementation (probably) handles everything for us too, so this can be implemented without thinking about recursion.
|
||||
pub fn clone_data(&self) -> Self {
|
||||
// TODO! implement CopyOnWrite. For now, just always copy. This also prevents mut references not existing since in ::Dat(cloned, _), cloned will always stay 0.
|
||||
return self.operate_on_data_immut(|v| v.clone()).to();
|
||||
// match &mut *self.0.lock().unwrap() {
|
||||
// VDataInner::Data(cloned, _data) => {
|
||||
// *cloned += 1;
|
||||
// VDataInner::ClonedFrom(self.clone_arc()).to()
|
||||
// }
|
||||
// VDataInner::Mut(inner) => inner.lock().unwrap().clone_data(),
|
||||
// VDataInner::ClonedFrom(inner) => inner.clone_data(),
|
||||
// }
|
||||
}
|
||||
/// clones self, returning a new instance of self that will always yield the same data as self, so that changes done to either are shared between both.
|
||||
pub fn clone_mut(&self) -> Self {
|
||||
VDataInner::Mut(Arc::new(Mutex::new(self.clone_arc()))).to()
|
||||
}
|
||||
fn clone_arc(&self) -> Self {
|
||||
#[cfg(not(debug_assertions))]
|
||||
return Self(Arc::clone(&self.0));
|
||||
#[cfg(debug_assertions)]
|
||||
return Self(Arc::clone(&self.0), self.1.clone());
|
||||
}
|
||||
pub fn operate_on_data_immut<F, O>(&self, func: F) -> O
|
||||
where
|
||||
F: FnOnce(&VDataEnum) -> O,
|
||||
{
|
||||
match &*self.0.lock().unwrap() {
|
||||
VDataInner::Data(_, data) => func(data.as_ref()),
|
||||
VDataInner::Mut(inner) => inner.lock().unwrap().operate_on_data_immut(func),
|
||||
VDataInner::ClonedFrom(inner) => inner.operate_on_data_immut(func),
|
||||
}
|
||||
}
|
||||
/// runs func on the underlying data.
|
||||
/// attempts to get a mutable reference to the data. if this fails, it will (partially) clone the data, then point the VData to the new data,
|
||||
/// so that other VDatas pointing to the same original data aren't changed.
|
||||
pub fn operate_on_data_mut<F, O>(&mut self, func: F) -> O
|
||||
where
|
||||
F: FnOnce(&mut VDataEnum) -> O,
|
||||
{
|
||||
let (new_val, o) = {
|
||||
let mut lock = self.0.lock().unwrap();
|
||||
match &mut *lock {
|
||||
VDataInner::Data(count, data) => {
|
||||
if *count == 0 {
|
||||
(None, func(data.as_mut()))
|
||||
} else {
|
||||
let mut new_data = data.clone();
|
||||
let o = func(new_data.as_mut());
|
||||
// *self doesn't modify the ::Data, it instead points the value that wraps it to a new ::Data, leaving the old one as it was.
|
||||
// for proof: data is untouched, only the new_data is ever modified.
|
||||
let new_vdata = VDataInner::Data(0, new_data).to();
|
||||
(Some(new_vdata), o)
|
||||
}
|
||||
}
|
||||
VDataInner::Mut(inner) => (None, inner.lock().unwrap().operate_on_data_mut(func)),
|
||||
VDataInner::ClonedFrom(inner) => (None, inner.operate_on_data_mut(func)),
|
||||
}
|
||||
};
|
||||
if let Some(nv) = new_val {
|
||||
*self = nv;
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
/// Since operate_on_data_mut can clone, it may be inefficient for just assigning (where we don't care about the previous value, so it doesn't need to be cloned).
|
||||
/// This is what this function is for. (TODO: actually make it more efficient instead of using operate_on_data_mut)
|
||||
pub fn assign_data(&mut self, new_data: VDataEnum) {
|
||||
let o = self.operate_on_data_mut(|d| *d = new_data);
|
||||
o
|
||||
}
|
||||
/// Assigns the new_data to self. Affects all muts pointing to the same data, but no ClonedFroms.
|
||||
pub fn assign(&mut self, new: VData) {
|
||||
self.assign_data(new.inner_cloned())
|
||||
// !PROBLEM! If ClonedFrom always has to point to a Data, this may break things!
|
||||
// match &mut *self.0.lock().unwrap() {
|
||||
// VDataInner::Data(count, data) => {
|
||||
// // *self doesn't modify the ::Data, it instead points the value that wraps it to a new ::Data, leaving the old one as it was.
|
||||
// // for proof: data is untouched.
|
||||
// *self = new_data;
|
||||
// }
|
||||
// VDataInner::Mut(inner) => inner.lock().unwrap().assign(new_data),
|
||||
// VDataInner::ClonedFrom(inner) => inner.assign(new_data),
|
||||
// }
|
||||
}
|
||||
/// assigns the value from self to assign_to if it's a reference, performs destructuring, and panics on invalid types that cannot be assigned to.
|
||||
pub fn assign_to(self: VData, mut assign_to: VData, info: &GSInfo) {
|
||||
// eprintln!("Assigning {self} to {assign_to}");
|
||||
assign_to.operate_on_data_mut(|assign_to| match assign_to {
|
||||
VDataEnum::Tuple(v) | VDataEnum::List(_, v) => {
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
self.get(i)
|
||||
.expect(
|
||||
"tried to assign to tuple, but value didn't return Some(_) on get()",
|
||||
)
|
||||
.assign_to(v.clone_data(), info)
|
||||
}
|
||||
}
|
||||
VDataEnum::Reference(r) => r.assign(self),
|
||||
o => todo!("ERR: Cannot assign to {o}."),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Drop for VDataInner {
|
||||
fn drop(&mut self) {
|
||||
if let Self::ClonedFrom(origin) = self {
|
||||
if let Self::Data(_ref_count, _data) = &mut *origin.0.lock().unwrap() {
|
||||
// *ref_count = ref_count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VData {
|
||||
/// this will always clone! if a reference or mutable reference is enough, use operate_on_data_* instead!
|
||||
pub fn inner_cloned(&self) -> VDataEnum {
|
||||
self.operate_on_data_immut(|v| v.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// - - make VData act like VDataEnum (as if it were real data) - -
|
||||
|
||||
impl Clone for VData {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_data()
|
||||
}
|
||||
}
|
||||
impl FormatGs for VData {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
self.operate_on_data_immut(|v| v.fmtgs(f, info, form, file))
|
||||
}
|
||||
}
|
||||
impl Debug for VData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.operate_on_data_immut(|v| Debug::fmt(v, f))
|
||||
}
|
||||
}
|
||||
impl Display for VData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.operate_on_data_immut(|v| Display::fmt(v, f))
|
||||
}
|
||||
}
|
||||
impl PartialEq for VData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.operate_on_data_immut(|a| other.operate_on_data_immut(|b| a == b))
|
||||
}
|
||||
}
|
||||
impl PartialEq<VDataEnum> for VData {
|
||||
fn eq(&self, other: &VDataEnum) -> bool {
|
||||
self.operate_on_data_immut(|a| a == other)
|
||||
}
|
||||
}
|
||||
impl PartialEq<VData> for VDataEnum {
|
||||
fn eq(&self, other: &VData) -> bool {
|
||||
other.operate_on_data_immut(|b| self == b)
|
||||
}
|
||||
}
|
||||
impl VData {
|
||||
pub fn out_single(&self) -> VSingleType {
|
||||
self.operate_on_data_immut(|v| v.out_single())
|
||||
}
|
||||
pub fn out(&self) -> VType {
|
||||
self.out_single().to()
|
||||
}
|
||||
pub fn noenum(&self) -> Self {
|
||||
if let Some(v) = self.operate_on_data_immut(|v| v.noenum()) {
|
||||
v
|
||||
} else {
|
||||
self.clone_data()
|
||||
}
|
||||
}
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
self.operate_on_data_immut(|v| v.safe_to_share())
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<VData> {
|
||||
self.operate_on_data_immut(|v| v.get(i))
|
||||
}
|
||||
pub fn get_ref(&mut self, i: usize) -> Option<VData> {
|
||||
self.operate_on_data_mut(|v| v.get_ref(i))
|
||||
}
|
||||
pub fn matches(&self) -> Option<Self> {
|
||||
match self.operate_on_data_immut(|v| v.matches()) {
|
||||
Some(Some(v)) => Some(v),
|
||||
Some(None) => Some(self.clone_data()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn deref(&self) -> Option<Self> {
|
||||
self.operate_on_data_immut(|v| v.deref())
|
||||
}
|
||||
}
|
||||
|
||||
// - - VDataEnum - -
|
||||
|
||||
impl Clone for VDataEnum {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
// exception: don't clone the value AND don't use CoW,
|
||||
// because we want to share the same Arc<Mutex<_>>.
|
||||
Self::Reference(r) => Self::Reference(r.clone_mut()),
|
||||
// default impls
|
||||
Self::Bool(b) => Self::Bool(*b),
|
||||
Self::Int(i) => Self::Int(*i),
|
||||
Self::Float(f) => Self::Float(*f),
|
||||
Self::String(s) => Self::String(s.clone()),
|
||||
Self::Tuple(v) => Self::Tuple(v.clone()),
|
||||
Self::List(t, v) => Self::List(t.clone(), v.clone()),
|
||||
Self::Function(f) => Self::Function(f.clone()),
|
||||
Self::Thread(th, ty) => Self::Thread(th.clone(), ty.clone()),
|
||||
Self::EnumVariant(v, d) => Self::EnumVariant(v.clone(), d.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq for VDataEnum {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Reference(a), Self::Reference(b)) => a == b,
|
||||
(Self::Reference(a), b) => a == b,
|
||||
(a, Self::Reference(b)) => a == b,
|
||||
(Self::Bool(a), Self::Bool(b)) => *a == *b,
|
||||
(Self::Int(a), Self::Int(b)) => *a == *b,
|
||||
(Self::Float(a), Self::Float(b)) => *a == *b,
|
||||
(Self::String(a), Self::String(b)) => *a == *b,
|
||||
(Self::Tuple(a), Self::Tuple(b)) | (Self::List(_, a), Self::List(_, b)) => {
|
||||
a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a == b)
|
||||
}
|
||||
(Self::EnumVariant(a1, a2), Self::EnumVariant(b1, b2)) => *a1 == *b1 && *a2 == *b2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VDataEnum {
|
||||
pub fn deref(&self) -> Option<VData> {
|
||||
if let Self::Reference(r) = self {
|
||||
Some(r.clone_mut())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn out_single(&self) -> VSingleType {
|
||||
match self {
|
||||
Self::Bool(..) => VSingleType::Bool,
|
||||
Self::Int(..) => VSingleType::Int,
|
||||
Self::Float(..) => VSingleType::Float,
|
||||
Self::String(..) => VSingleType::String,
|
||||
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out_single().to()).collect()),
|
||||
Self::List(t, _) => VSingleType::List(t.clone()),
|
||||
Self::Function(f) => VSingleType::Function(f.out_map.clone()),
|
||||
Self::Thread(_, o) => VSingleType::Thread(o.clone()),
|
||||
Self::Reference(r) => VSingleType::Reference(r.out()),
|
||||
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out_single().to()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get()
|
||||
impl VDataEnum {
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
match self {
|
||||
Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Function(_) => {
|
||||
true
|
||||
}
|
||||
Self::Tuple(v) | Self::List(_, v) => v.iter().all(|v| v.safe_to_share()),
|
||||
Self::Thread(..) | Self::Reference(..) | Self::EnumVariant(..) => false,
|
||||
}
|
||||
}
|
||||
pub fn noenum(&self) -> Option<VData> {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) => Some(v.clone_data()),
|
||||
_v => None,
|
||||
}
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<VData> {
|
||||
match self {
|
||||
Self::Bool(..)
|
||||
| Self::Int(..)
|
||||
| Self::Float(..)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
Self::String(s) => match s.chars().nth(i) {
|
||||
// Slow!
|
||||
Some(ch) => Some(Self::String(format!("{ch}")).to()),
|
||||
None => None,
|
||||
},
|
||||
Self::Tuple(v) | Self::List(_, v) => v.get(i).cloned(),
|
||||
Self::Reference(r) => r.clone_mut().get_ref(i),
|
||||
Self::EnumVariant(_, v) => v.get(i),
|
||||
}
|
||||
}
|
||||
/// this is guaranteed to return Self::Reference(_), if it returns Some(_).
|
||||
pub fn get_ref(&mut self, i: usize) -> Option<VData> {
|
||||
Some(Self::Reference(self.get_ref_inner(i)?).to())
|
||||
}
|
||||
pub fn get_ref_inner(&mut self, i: usize) -> Option<VData> {
|
||||
match self {
|
||||
Self::Bool(..)
|
||||
| Self::Int(..)
|
||||
| Self::Float(..)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
// TODO: String
|
||||
Self::String(_s) => None,
|
||||
Self::Tuple(v) | Self::List(_, v) => v.get(i).map(|v| v.clone_mut()),
|
||||
Self::Reference(r) => r.get_ref(i),
|
||||
Self::EnumVariant(_, v) => v.get_ref(i),
|
||||
}
|
||||
}
|
||||
/// Some(None) => matches with self
|
||||
pub fn matches(&self) -> Option<Option<VData>> {
|
||||
match self {
|
||||
VDataEnum::Tuple(tuple) => tuple.get(0).cloned().map(|v| Some(v)),
|
||||
VDataEnum::Bool(v) => {
|
||||
if *v {
|
||||
Some(Some(VDataEnum::Bool(true).to()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VDataEnum::EnumVariant(..) => None,
|
||||
_other => Some(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VSingleType {
|
||||
/// returns (can_fail_to_match, matches_as)
|
||||
pub fn matches(&self) -> (bool, VType) {
|
||||
match self {
|
||||
Self::Tuple(v) => match v.first() {
|
||||
Some(v) => (false, v.clone()),
|
||||
None => (true, VType { types: vec![] }),
|
||||
},
|
||||
Self::Bool => (true, Self::Bool.to()),
|
||||
Self::EnumVariant(..) | Self::EnumVariantS(..) => (true, VType { types: vec![] }),
|
||||
v => (false, v.clone().to()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
/// returns (can_fail_to_match, matches_as)
|
||||
pub fn matches(&self, info: &GlobalScriptInfo) -> (bool, VType) {
|
||||
let mut can_fail = false;
|
||||
let mut matches_as = VType { types: vec![] };
|
||||
for t in self.types.iter() {
|
||||
let (f, t) = t.matches();
|
||||
can_fail |= f;
|
||||
matches_as.add_types(t, info);
|
||||
}
|
||||
(can_fail, matches_as)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod thread {
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
thread::JoinHandle,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{VData, VDataEnum};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VDataThread(Arc<Mutex<VDataThreadEnum>>);
|
||||
impl VDataThread {
|
||||
pub fn try_get(&self) -> Option<VData> {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(_) => None,
|
||||
VDataThreadEnum::Finished(v) => Some(v.clone()),
|
||||
}
|
||||
}
|
||||
pub fn get(&self) -> VData {
|
||||
let dur = Duration::from_millis(100);
|
||||
loop {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
while !v.is_finished() {
|
||||
std::thread::sleep(dur);
|
||||
}
|
||||
}
|
||||
VDataThreadEnum::Finished(v) => return v.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn lock(&self) -> std::sync::MutexGuard<VDataThreadEnum> {
|
||||
let mut mg = self.0.lock().unwrap();
|
||||
match &*mg {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
if v.is_finished() {
|
||||
let m = std::mem::replace(
|
||||
&mut *mg,
|
||||
VDataThreadEnum::Finished(VDataEnum::Bool(false).to()),
|
||||
);
|
||||
match m {
|
||||
VDataThreadEnum::Running(v) => {
|
||||
*mg = VDataThreadEnum::Finished(v.join().unwrap())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
mg
|
||||
}
|
||||
}
|
||||
impl Debug for VDataThread {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &*self.lock() {
|
||||
VDataThreadEnum::Running(_) => write!(f, "(thread running)"),
|
||||
VDataThreadEnum::Finished(_v) => write!(f, "(thread finished)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum VDataThreadEnum {
|
||||
Running(JoinHandle<VData>),
|
||||
Finished(VData),
|
||||
}
|
||||
impl VDataThreadEnum {
|
||||
pub fn to(self) -> VDataThread {
|
||||
VDataThread(Arc::new(Mutex::new(self)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct VDataWInfo(VData, GSInfo);
|
||||
impl Display for VDataWInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmtgs(
|
||||
f,
|
||||
Some(&self.1),
|
||||
&mut super::fmtgs::FormatInfo::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl VData {
|
||||
pub fn gsi(self, info: GSInfo) -> VDataWInfo {
|
||||
VDataWInfo(self, info)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VDataEnum {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Bool(true) => write!(f, "true"),
|
||||
Self::Bool(false) => write!(f, "false"),
|
||||
Self::Int(v) => write!(f, "{v}"),
|
||||
Self::Float(v) => write!(f, "{v}"),
|
||||
Self::String(v) => write!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
form.value_string_quotes(info, "\"".to_owned()),
|
||||
form.value_string_content(info, v.to_owned()),
|
||||
form.value_string_quotes(info, "\"".to_owned())
|
||||
),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "[")?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::List(_t, v) => {
|
||||
write!(f, "[")?;
|
||||
for (_i, v) in v.iter().enumerate() {
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "...]")
|
||||
}
|
||||
Self::Function(func) => {
|
||||
VSingleType::Function(func.out_map.clone()).fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::Thread(..) => write!(f, "[TODO] THREAD"),
|
||||
Self::Reference(inner) => {
|
||||
write!(f, "&")?;
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
if let Some(name) = if let Some(info) = info {
|
||||
info.enum_variants.iter().find_map(|(name, id)| {
|
||||
if id == variant {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
write!(f, "{name}: ")?;
|
||||
} else {
|
||||
write!(f, "{variant}: ")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for VDataEnum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
@@ -1,593 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
};
|
||||
|
||||
use super::{
|
||||
code_runnable::{RFunction, RFunctionType},
|
||||
fmtgs::FormatGs,
|
||||
global_info::{GSInfo, GlobalScriptInfo},
|
||||
};
|
||||
|
||||
use super::global_info::LogMsg;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VType {
|
||||
pub types: Vec<VSingleType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VSingleType {
|
||||
Any,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Tuple(Vec<VType>),
|
||||
List(VType),
|
||||
Function(Vec<(Vec<VType>, VType)>),
|
||||
Thread(VType),
|
||||
Reference(VType),
|
||||
EnumVariant(usize, VType),
|
||||
EnumVariantS(String, VType),
|
||||
CustomType(usize),
|
||||
CustomTypeS(String),
|
||||
}
|
||||
|
||||
impl VType {
|
||||
pub fn empty() -> Self {
|
||||
Self { types: vec![] }
|
||||
}
|
||||
pub fn get(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get(i, info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
pub fn get_always(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get_always(i, info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
/// returns Some(true) or Some(false) if all types are references or not references. If it is mixed or types is empty, returns None.
|
||||
pub fn is_reference(&self) -> Option<bool> {
|
||||
let mut noref = false;
|
||||
let mut reference = false;
|
||||
for t in &self.types {
|
||||
if t.is_reference() {
|
||||
reference = true;
|
||||
} else {
|
||||
noref = true;
|
||||
}
|
||||
}
|
||||
if noref != reference {
|
||||
Some(reference)
|
||||
} else {
|
||||
// either empty (false == false) or mixed (true == true)
|
||||
None
|
||||
}
|
||||
}
|
||||
/// returns Some(t) where t is the type you get from dereferencing self or None if self contains even a single type that cannot be dereferenced.
|
||||
pub fn dereference(&self, info: &GlobalScriptInfo) -> Option<Self> {
|
||||
let mut out = Self::empty();
|
||||
for t in self.types.iter() {
|
||||
out.add_types(t.deref()?, info);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
pub fn reference(&self) -> Self {
|
||||
VSingleType::Reference(self.clone()).to()
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
/// None => Cannot get, Some(t) => getting can return t or nothing
|
||||
pub fn get(&self, i: usize, gsinfo: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..)
|
||||
| Self::EnumVariant(..)
|
||||
| Self::EnumVariantS(..) => None,
|
||||
Self::String => Some(VSingleType::String.into()),
|
||||
Self::Tuple(t) => t.get(i).cloned(),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => Some(r.get(i, gsinfo)?.reference()),
|
||||
Self::CustomType(t) => gsinfo.custom_types[*t].get(i, gsinfo),
|
||||
&Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomTypeS instead of CustomType, compiler bug? [get]")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_any(&self, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..) => None,
|
||||
Self::String => Some(VSingleType::String.into()),
|
||||
Self::Tuple(t) => Some(t.iter().fold(VType::empty(), |mut a, b| {
|
||||
a.add_typesr(b, info);
|
||||
a
|
||||
})),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => Some(VType {
|
||||
// this is &a/&b/..., NOT &(a/b/...)!
|
||||
types: r
|
||||
.get_any(info)?
|
||||
.types
|
||||
.iter()
|
||||
.map(|v| VSingleType::Reference(v.clone().to()))
|
||||
.collect(),
|
||||
}),
|
||||
Self::EnumVariant(_, t) => t.get_any(info),
|
||||
Self::EnumVariantS(..) => unreachable!(),
|
||||
Self::CustomType(t) => info.custom_types[*t].get_any(info),
|
||||
Self::CustomTypeS(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
/// None => might not always return t, Some(t) => can only return t
|
||||
pub fn get_always(&self, i: usize, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
| Self::Int
|
||||
| Self::Float
|
||||
| Self::String
|
||||
| Self::List(_)
|
||||
| Self::Function(..)
|
||||
| Self::Thread(..)
|
||||
| Self::EnumVariant(..)
|
||||
| Self::EnumVariantS(..) => None,
|
||||
Self::Tuple(t) => t.get(i).cloned(),
|
||||
Self::Reference(r) => Some(VSingleType::Reference(r.get_any(info)?).to()),
|
||||
Self::CustomType(t) => info.custom_types[*t].get_always(i, info),
|
||||
Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomTypeS instead of CustomType, compiler bug? [get_always]")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn is_reference(&self) -> bool {
|
||||
match self {
|
||||
Self::Reference(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn deref(&self) -> Option<VType> {
|
||||
if let Self::Reference(v) = self {
|
||||
Some(v.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn get_any(&self, info: &GlobalScriptInfo) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.get_any(info)?, info); // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl VType {
|
||||
/// Returns a vec with all types in self that aren't covered by rhs. If the returned vec is empty, self fits in rhs.
|
||||
pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> Vec<VSingleType> {
|
||||
let mut no = vec![];
|
||||
for t in &self.types {
|
||||
// if t doesnt fit in any of rhs's types
|
||||
if !t.fits_in_type(rhs, info) {
|
||||
no.push(t.clone())
|
||||
}
|
||||
}
|
||||
if info.log.vtype_fits_in.log() {
|
||||
info.log
|
||||
.log(LogMsg::VTypeFitsIn(self.clone(), rhs.clone(), no.clone()))
|
||||
}
|
||||
no
|
||||
}
|
||||
pub fn inner_types_for_iters(&self, info: &GlobalScriptInfo) -> VType {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out.add_types(t.inner_types_for_iters(info), info);
|
||||
}
|
||||
out
|
||||
}
|
||||
pub fn enum_variants(&mut self, enum_variants: &mut HashMap<String, usize>) {
|
||||
for t in &mut self.types {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
pub fn contains(&self, t: &VSingleType, info: &GlobalScriptInfo) -> bool {
|
||||
t.fits_in_type(self, info)
|
||||
}
|
||||
pub fn noenum(self, info: &GlobalScriptInfo) -> Self {
|
||||
let mut o = Self { types: vec![] };
|
||||
for t in self.types {
|
||||
o.add_types(t.noenum(), info);
|
||||
}
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
impl VType {
|
||||
pub fn eq(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
self.fits_in(rhs, info).is_empty() && rhs.fits_in(self, info).is_empty()
|
||||
}
|
||||
}
|
||||
impl VSingleType {
|
||||
pub fn eq(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
self.fits_in(rhs, info) && rhs.fits_in(self, info)
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn add_types(&mut self, new: Self, info: &GlobalScriptInfo) {
|
||||
for t in new.types {
|
||||
self.add_type(t, info)
|
||||
}
|
||||
}
|
||||
pub fn add_type(&mut self, new: VSingleType, info: &GlobalScriptInfo) {
|
||||
if !self.contains(&new, info) {
|
||||
self.types.push(new)
|
||||
}
|
||||
}
|
||||
pub fn add_typesr(&mut self, new: &Self, info: &GlobalScriptInfo) {
|
||||
for t in &new.types {
|
||||
self.add_typer(t, info)
|
||||
}
|
||||
}
|
||||
pub fn add_typer(&mut self, new: &VSingleType, info: &GlobalScriptInfo) {
|
||||
if !self.contains(new, info) {
|
||||
self.types.push(new.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
pub fn to(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
pub fn inner_types_for_iters(&self, info: &GlobalScriptInfo) -> VType {
|
||||
match self {
|
||||
Self::Tuple(v) => {
|
||||
let mut out = VType::empty();
|
||||
for it in v {
|
||||
out.add_typesr(it, info);
|
||||
}
|
||||
out
|
||||
}
|
||||
Self::List(v) => v.clone(),
|
||||
// NOTE: to make ints work in for loops
|
||||
Self::Int => Self::Int.to(),
|
||||
// for iterators in for loops: the match of the function's returned value make up the inner type
|
||||
Self::Function(f) => {
|
||||
// function that takes no inputs
|
||||
if let Some(out) = f.iter().find_map(|(args, out)| {
|
||||
if args.is_empty() {
|
||||
Some(out.clone().matches(info).1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
out
|
||||
} else {
|
||||
VType::empty()
|
||||
}
|
||||
}
|
||||
Self::Reference(r) => r.inner_types_for_iters(info).reference(),
|
||||
_ => VType::empty(),
|
||||
}
|
||||
}
|
||||
pub fn noenum(self) -> VType {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) | Self::EnumVariantS(_, v) => v,
|
||||
v => v.to(),
|
||||
}
|
||||
}
|
||||
/// converts all Self::EnumVariantS to Self::EnumVariant
|
||||
pub fn enum_variants(&mut self, enum_variants: &mut HashMap<String, usize>) {
|
||||
match self {
|
||||
Self::Any | Self::Bool | Self::Int | Self::Float | Self::String => (),
|
||||
Self::Tuple(v) => {
|
||||
for t in v {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
Self::List(t) => t.enum_variants(enum_variants),
|
||||
Self::Function(f) => {
|
||||
for f in f {
|
||||
for t in &mut f.0 {
|
||||
t.enum_variants(enum_variants);
|
||||
}
|
||||
f.1.enum_variants(enum_variants);
|
||||
}
|
||||
}
|
||||
Self::Thread(v) => v.enum_variants(enum_variants),
|
||||
Self::Reference(v) => v.enum_variants(enum_variants),
|
||||
Self::EnumVariant(_e, v) => v.enum_variants(enum_variants),
|
||||
Self::EnumVariantS(e, v) => {
|
||||
let e = if let Some(e) = enum_variants.get(e) {
|
||||
*e
|
||||
} else {
|
||||
let v = enum_variants.len();
|
||||
enum_variants.insert(e.clone(), v);
|
||||
v
|
||||
};
|
||||
v.enum_variants(enum_variants);
|
||||
*self = Self::EnumVariant(e, v.clone());
|
||||
}
|
||||
Self::CustomType(_) | Self::CustomTypeS(_) => (),
|
||||
}
|
||||
}
|
||||
pub fn fits_in(&self, rhs: &Self, info: &GlobalScriptInfo) -> bool {
|
||||
let o = match (self, rhs) {
|
||||
(_, Self::Any) => true,
|
||||
(Self::Any, _) => false,
|
||||
// references have to be eq, not fits_in; otherwise whoever gets our reference could write invalid data to it!
|
||||
(Self::Reference(a), Self::Reference(b)) => a.eq(b, info),
|
||||
(Self::Reference(_), _) | (_, Self::Reference(_)) => false,
|
||||
(Self::EnumVariant(v1, t1), Self::EnumVariant(v2, t2)) => {
|
||||
*v1 == *v2 && t1.fits_in(&t2, info).is_empty()
|
||||
}
|
||||
(Self::CustomType(a), Self::CustomType(b)) => *a == *b, /* || info.custom_types[*a].fits_in(&info.custom_types[*b], info).is_empty() */
|
||||
(Self::CustomType(a), b) => info.custom_types[*a]
|
||||
.fits_in(&b.clone().to(), info)
|
||||
.is_empty(),
|
||||
(a, Self::CustomType(b)) => a
|
||||
.clone()
|
||||
.to()
|
||||
.fits_in(&info.custom_types[*b], info)
|
||||
.is_empty(),
|
||||
(Self::CustomTypeS(_), _) | (_, Self::CustomTypeS(_)) => {
|
||||
unreachable!("CustomTypeS instead of CustomType - compiler bug?")
|
||||
}
|
||||
(Self::EnumVariant(..), _) | (_, Self::EnumVariant(..)) => false,
|
||||
(Self::EnumVariantS(..), _) | (_, Self::EnumVariantS(..)) => {
|
||||
unreachable!("EnumVariantS instead of EnumVariant - compiler bug?")
|
||||
}
|
||||
(Self::Bool, Self::Bool)
|
||||
| (Self::Int, Self::Int)
|
||||
| (Self::Float, Self::Float)
|
||||
| (Self::String, Self::String) => true,
|
||||
(Self::Bool | Self::Int | Self::Float | Self::String, _) => false,
|
||||
(Self::Tuple(a), Self::Tuple(b)) => {
|
||||
if a.len() == b.len() {
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.all(|(a, b)| a.fits_in(b, info).is_empty())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Self::Tuple(_), _) => false,
|
||||
(Self::List(a), Self::List(b)) => a.fits_in(b, info).is_empty(),
|
||||
(Self::List(_), _) => false,
|
||||
(Self::Function(a), Self::Function(b)) => 'fnt: {
|
||||
// since RFunction.out only uses out_map, we can create a dummy RFunction here.
|
||||
let af = RFunction {
|
||||
statement: RFunctionType::Dummy,
|
||||
out_map: a.clone(),
|
||||
};
|
||||
for (ins, out) in b {
|
||||
// try everything that would be valid for b
|
||||
if let Some(v) = af.out_by_map(ins, info) {
|
||||
if !v.fits_in(out, info).is_empty() {
|
||||
// found something that's valid for both, but a returns something that doesn't fit in what b would have returned -> a doesn't fit.
|
||||
break 'fnt false;
|
||||
}
|
||||
} else {
|
||||
// found something that's valid for b but not for a -> a doesn't fit.
|
||||
break 'fnt false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
(Self::Function(..), _) => false,
|
||||
(Self::Thread(a), Self::Thread(b)) => a.fits_in(b, info).is_empty(),
|
||||
(Self::Thread(..), _) => false,
|
||||
};
|
||||
if info.log.vsingletype_fits_in.log() {
|
||||
info.log
|
||||
.log(LogMsg::VSingleTypeFitsIn(self.clone(), rhs.clone(), o));
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn fits_in_type(&self, rhs: &VType, info: &GlobalScriptInfo) -> bool {
|
||||
match self {
|
||||
Self::CustomType(t) => {
|
||||
rhs.types.iter().any(|rhs| {
|
||||
if let Self::CustomType(rhs) = rhs {
|
||||
*t == *rhs
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) || info.custom_types[*t].fits_in(rhs, info).is_empty()
|
||||
}
|
||||
_ => rhs.types.iter().any(|b| self.fits_in(b, info)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<VType> for VSingleType {
|
||||
fn into(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct VTypeWInfo(VType, GSInfo);
|
||||
impl Display for VTypeWInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmtgs(
|
||||
f,
|
||||
Some(&self.1),
|
||||
&mut super::fmtgs::FormatInfo::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn gsi(self, info: GSInfo) -> VTypeWInfo {
|
||||
VTypeWInfo(self, info)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VSingleType {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
Self::Bool => write!(f, "bool"),
|
||||
Self::Int => write!(f, "int"),
|
||||
Self::Float => write!(f, "float"),
|
||||
Self::String => write!(f, "string"),
|
||||
Self::Tuple(v) => {
|
||||
write!(f, "[")?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::List(v) => {
|
||||
write!(f, "[")?;
|
||||
v.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ...]")
|
||||
}
|
||||
Self::Function(func) => {
|
||||
write!(f, "fn(")?;
|
||||
for (inputs, output) in func {
|
||||
write!(f, "(")?;
|
||||
for i in inputs {
|
||||
i.fmtgs(f, info, form, file)?;
|
||||
write!(f, " ")?;
|
||||
}
|
||||
output.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::Thread(out) => {
|
||||
write!(f, "thread(")?;
|
||||
out.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::Reference(inner) => {
|
||||
if inner.types.len() != 1 {
|
||||
write!(f, "&(")?;
|
||||
} else {
|
||||
write!(f, "&")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
if inner.types.len() != 1 {
|
||||
write!(f, ")")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::EnumVariant(variant, inner) => {
|
||||
if let Some(name) = if let Some(info) = info {
|
||||
info.enum_variants.iter().find_map(|(name, id)| {
|
||||
if id == variant {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
write!(f, "{name}(")?;
|
||||
} else {
|
||||
write!(f, "{variant}(")?;
|
||||
}
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::EnumVariantS(name, inner) => {
|
||||
write!(f, "{name}(")?;
|
||||
inner.fmtgs(f, info, form, file)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
Self::CustomType(t) => {
|
||||
if let Some(info) = info {
|
||||
#[cfg(not(debug_assertions))]
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
info.custom_type_names
|
||||
.iter()
|
||||
.find_map(|(name, id)| if *t == *id {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
)?;
|
||||
#[cfg(debug_assertions)]
|
||||
write!(
|
||||
f,
|
||||
"{}/*{}*/",
|
||||
info.custom_type_names
|
||||
.iter()
|
||||
.find_map(|(name, id)| if *t == *id {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap(),
|
||||
&info.custom_types[*t]
|
||||
)?;
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, "[custom type #{t}]")
|
||||
}
|
||||
}
|
||||
Self::CustomTypeS(t) => write!(f, "{t}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for VSingleType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatGs for VType {
|
||||
fn fmtgs(
|
||||
&self,
|
||||
f: &mut Formatter,
|
||||
info: Option<&GlobalScriptInfo>,
|
||||
form: &mut super::fmtgs::FormatInfo,
|
||||
file: Option<&crate::parsing::file::File>,
|
||||
) -> std::fmt::Result {
|
||||
for (i, t) in self.types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, "/")?;
|
||||
}
|
||||
t.fmtgs(f, info, form, file)?;
|
||||
}
|
||||
write!(f, ",")
|
||||
}
|
||||
}
|
||||
impl Display for VType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmtgs(f, None, &mut super::fmtgs::FormatInfo::default(), None)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
mod inlib;
|
||||
mod lang;
|
||||
mod libs;
|
||||
mod parsing;
|
||||
mod pathutil;
|
||||
|
||||
pub use inlib::MyLib;
|
||||
pub use lang::{fmtgs, global_info::GlobalScriptInfo, val_data::*, val_type::*};
|
||||
pub use libs::comms::{ByteData, ByteDataA, Message, RespondableMessage};
|
||||
pub use parsing::{parse::*, *};
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
lang::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
},
|
||||
MyLib, RespondableMessage,
|
||||
};
|
||||
}
|
||||
@@ -1,540 +0,0 @@
|
||||
use crate::lang::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
/// any message implements this trait. reponding to the message with A generates the response of type B.
|
||||
pub trait RespondableMessage: MessageResponse {
|
||||
type With;
|
||||
type Response: MessageResponse;
|
||||
fn respond(self, with: Self::With) -> Self::Response;
|
||||
}
|
||||
|
||||
/// any message or response implements this trait
|
||||
pub trait MessageResponse: ByteData + ByteDataA {
|
||||
fn messagetype_id() -> u32;
|
||||
fn msgtype_id(&self) -> u32 {
|
||||
Self::messagetype_id()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ByteData: Sized {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read;
|
||||
}
|
||||
/// for things like &str, which can't be created easily (String exists for that purpose), but can still be converted to bytes.
|
||||
pub trait ByteDataA {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>);
|
||||
fn as_byte_data_vec(&self) -> Vec<u8> {
|
||||
let mut vec = Vec::new();
|
||||
self.as_byte_data(&mut vec);
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
RunFunction(run_function::Message),
|
||||
}
|
||||
impl ByteData for Message {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let type_id = u32::from_byte_data(data)?;
|
||||
Ok(match type_id {
|
||||
0 => Self::RunFunction(ByteData::from_byte_data(data)?),
|
||||
_other => unreachable!("read unknown type_id byte for message!"),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<run_function::Message> for Message {
|
||||
fn from(value: run_function::Message) -> Self {
|
||||
Self::RunFunction(value)
|
||||
}
|
||||
}
|
||||
|
||||
// implementations for the message/response pairs
|
||||
|
||||
pub mod run_function {
|
||||
use crate::lang::val_data::VData;
|
||||
|
||||
use super::{ByteData, ByteDataA, MessageResponse, RespondableMessage};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
pub function_id: u64,
|
||||
pub args: Vec<VData>,
|
||||
}
|
||||
pub struct Response {
|
||||
pub result: VData,
|
||||
}
|
||||
impl RespondableMessage for Message {
|
||||
type With = VData;
|
||||
type Response = Response;
|
||||
fn respond(self, with: Self::With) -> Self::Response {
|
||||
Response { result: with }
|
||||
}
|
||||
}
|
||||
impl MessageResponse for Message {
|
||||
fn messagetype_id() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
impl MessageResponse for Response {
|
||||
fn messagetype_id() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
impl ByteDataA for Message {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.function_id.as_byte_data(vec);
|
||||
self.args.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl ByteData for Message {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
function_id: ByteData::from_byte_data(data)?,
|
||||
args: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for Response {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.result.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl ByteData for Response {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
result: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implementations of ByteData for other data
|
||||
|
||||
type UsizeConstLen = u64;
|
||||
type IsizeConstLen = i64;
|
||||
|
||||
impl ByteDataA for usize {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self as UsizeConstLen).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for usize {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(UsizeConstLen::from_byte_data(data)? as _)
|
||||
}
|
||||
}
|
||||
impl ByteDataA for isize {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self as IsizeConstLen).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for isize {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(IsizeConstLen::from_byte_data(data)? as _)
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i32 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i32 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 4];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u32 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u32 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 4];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for u128 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for u128 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 16];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for i128 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes())
|
||||
}
|
||||
}
|
||||
impl ByteData for i128 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 16];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for f64 {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
vec.extend_from_slice(&self.to_be_bytes());
|
||||
}
|
||||
}
|
||||
impl ByteData for f64 {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut b = [0u8; 8];
|
||||
data.read_exact(&mut b)?;
|
||||
Ok(Self::from_be_bytes(b))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for String {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.len().as_byte_data(vec);
|
||||
vec.extend_from_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
impl ByteData for String {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let len = ByteData::from_byte_data(data)?;
|
||||
let mut buf = vec![0; len];
|
||||
data.read_exact(buf.as_mut_slice())?;
|
||||
let str = String::from_utf8(buf).unwrap();
|
||||
Ok(str)
|
||||
}
|
||||
}
|
||||
impl<T> ByteDataA for &T
|
||||
where
|
||||
T: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
(*self).as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl<T> ByteDataA for Vec<T>
|
||||
where
|
||||
T: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.len().as_byte_data(vec);
|
||||
for elem in self {
|
||||
elem.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> ByteData for Vec<T>
|
||||
where
|
||||
T: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let len = usize::from_byte_data(data)?;
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
vec.push(T::from_byte_data(data)?);
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
impl<A, B> ByteDataA for (A, B)
|
||||
where
|
||||
A: ByteDataA,
|
||||
B: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.0.as_byte_data(vec);
|
||||
self.1.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl<A, B> ByteData for (A, B)
|
||||
where
|
||||
A: ByteData,
|
||||
B: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok((
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl<A, B, C, D, E> ByteDataA for (A, B, C, D, E)
|
||||
where
|
||||
A: ByteDataA,
|
||||
B: ByteDataA,
|
||||
C: ByteDataA,
|
||||
D: ByteDataA,
|
||||
E: ByteDataA,
|
||||
{
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.0.as_byte_data(vec);
|
||||
self.1.as_byte_data(vec);
|
||||
self.2.as_byte_data(vec);
|
||||
self.3.as_byte_data(vec);
|
||||
self.4.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
impl<A, B, C, D, E> ByteData for (A, B, C, D, E)
|
||||
where
|
||||
A: ByteData,
|
||||
B: ByteData,
|
||||
C: ByteData,
|
||||
D: ByteData,
|
||||
E: ByteData,
|
||||
{
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok((
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VType {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.types.as_byte_data(vec)
|
||||
}
|
||||
}
|
||||
impl ByteData for VType {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(Self {
|
||||
types: ByteData::from_byte_data(data)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VSingleType {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
match self {
|
||||
Self::Any => vec.push(b'a'),
|
||||
Self::Bool => vec.push(b'b'),
|
||||
Self::Int => vec.push(b'i'),
|
||||
Self::Float => vec.push(b'f'),
|
||||
Self::String => vec.push(b'"'),
|
||||
Self::Tuple(v) => {
|
||||
vec.push(b't');
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::List(v) => {
|
||||
vec.push(b'l');
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::Function(f) => {
|
||||
vec.push(b'F');
|
||||
f.as_byte_data(vec);
|
||||
}
|
||||
Self::Thread(r) => {
|
||||
vec.push(b'T');
|
||||
r.as_byte_data(vec);
|
||||
}
|
||||
Self::Reference(r) => {
|
||||
vec.push(b'R');
|
||||
r.as_byte_data(vec);
|
||||
}
|
||||
Self::EnumVariant(e, v) => {
|
||||
vec.push(b'e');
|
||||
e.as_byte_data(vec);
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::EnumVariantS(e, v) => {
|
||||
vec.push(b'E');
|
||||
e.as_byte_data(vec);
|
||||
v.as_byte_data(vec);
|
||||
}
|
||||
Self::CustomType(_) | Self::CustomTypeS(_) => {
|
||||
unreachable!("CustomType and CustomTypeS cannot be used in libraries [yet?].")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ByteData for VSingleType {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut switch_byte = [0u8];
|
||||
data.read_exact(&mut switch_byte)?;
|
||||
Ok(match switch_byte[0] {
|
||||
b'a' => Self::Any,
|
||||
b'b' => Self::Bool,
|
||||
b'i' => Self::Int,
|
||||
b'f' => Self::Float,
|
||||
b'"' => Self::String,
|
||||
b't' => Self::Tuple(ByteData::from_byte_data(data)?),
|
||||
b'l' => Self::List(ByteData::from_byte_data(data)?),
|
||||
b'F' => Self::Function(ByteData::from_byte_data(data)?),
|
||||
b'T' => Self::Thread(ByteData::from_byte_data(data)?),
|
||||
b'R' => Self::Reference(ByteData::from_byte_data(data)?),
|
||||
b'e' => Self::EnumVariant(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
b'E' => Self::EnumVariantS(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
_ => unreachable!("unexpected byte while reading single type"),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VData {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
self.operate_on_data_immut(|v| v.as_byte_data(vec))
|
||||
}
|
||||
}
|
||||
impl ByteData for VData {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
Ok(VDataEnum::from_byte_data(data)?.to())
|
||||
}
|
||||
}
|
||||
impl ByteDataA for VDataEnum {
|
||||
fn as_byte_data(&self, vec: &mut Vec<u8>) {
|
||||
match self {
|
||||
Self::Bool(false) => vec.push(b'b'),
|
||||
Self::Bool(true) => vec.push(b'B'),
|
||||
Self::Int(num) => {
|
||||
vec.push(b'i');
|
||||
num.as_byte_data(vec);
|
||||
}
|
||||
Self::Float(num) => {
|
||||
vec.push(b'f');
|
||||
num.as_byte_data(vec);
|
||||
}
|
||||
Self::String(s) => {
|
||||
vec.push(b'"');
|
||||
s.as_byte_data(vec);
|
||||
}
|
||||
Self::Tuple(c) => {
|
||||
vec.push(b't');
|
||||
c.as_byte_data(vec);
|
||||
}
|
||||
Self::List(t, data) => {
|
||||
vec.push(b'l');
|
||||
t.as_byte_data(vec);
|
||||
data.as_byte_data(vec);
|
||||
}
|
||||
// TODO?
|
||||
Self::Function(_) => vec.push(b'F'),
|
||||
Self::Thread(..) => vec.push(b'T'),
|
||||
Self::Reference(_r) => vec.push(b'R'),
|
||||
Self::EnumVariant(enum_id, inner) => {
|
||||
vec.push(b'E');
|
||||
enum_id.as_byte_data(vec);
|
||||
inner.as_byte_data(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ByteData for VDataEnum {
|
||||
fn from_byte_data<R>(data: &mut R) -> Result<Self, std::io::Error>
|
||||
where
|
||||
R: std::io::Read,
|
||||
{
|
||||
let mut switch_byte = [0u8];
|
||||
data.read_exact(&mut switch_byte)?;
|
||||
Ok(match switch_byte[0] {
|
||||
b'b' => Self::Bool(false),
|
||||
b'B' => Self::Bool(true),
|
||||
b'i' => Self::Int(ByteData::from_byte_data(data)?),
|
||||
b'f' => Self::Float(ByteData::from_byte_data(data)?),
|
||||
b'"' => Self::String(ByteData::from_byte_data(data)?),
|
||||
b't' => Self::Tuple(ByteData::from_byte_data(data)?),
|
||||
b'l' => Self::List(
|
||||
ByteData::from_byte_data(data)?,
|
||||
ByteData::from_byte_data(data)?,
|
||||
),
|
||||
b'E' => Self::EnumVariant(
|
||||
ByteData::from_byte_data(data)?,
|
||||
Box::new(ByteData::from_byte_data(data)?),
|
||||
),
|
||||
_ => unreachable!("read invalid byte"),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
pub mod comms;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{self, BufReader, Write},
|
||||
process::{Child, ChildStdin, ChildStdout, Command, Stdio},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::lang::{
|
||||
global_info::GlobalScriptInfo, to_runnable::ToRunnableError, val_data::VData, val_type::VType,
|
||||
};
|
||||
|
||||
use self::comms::{ByteData, ByteDataA, RespondableMessage};
|
||||
|
||||
// Libraries are processes that communicate via stdout/stdin.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Lib {
|
||||
name: String,
|
||||
process: Child,
|
||||
current_id: Arc<Mutex<u128>>,
|
||||
stdin: Arc<Mutex<ChildStdin>>,
|
||||
// stdout: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
task_sender: Arc<
|
||||
Mutex<std::sync::mpsc::Sender<(u128, Box<dyn FnOnce(&mut BufReader<ChildStdout>) + Send>)>>,
|
||||
>,
|
||||
pub registered_fns: Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
}
|
||||
impl Drop for Lib {
|
||||
fn drop(&mut self) {
|
||||
if self.process.try_wait().is_err() {
|
||||
if let Err(e) = self.process.kill() {
|
||||
eprint!(
|
||||
"Warn: tried to kill lib process for library \"{}\", but failed: {e:?}",
|
||||
self.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Sent by the library to request initialization
|
||||
/// ([ver_major], [ver_minor], [name], [desc], [registered_functions])
|
||||
pub type LibInitReq<'a> = (
|
||||
u32,
|
||||
u32,
|
||||
String,
|
||||
String,
|
||||
Vec<(String, Vec<(Vec<VType>, VType)>)>,
|
||||
);
|
||||
/// Sent by mers to finish initializing a library.
|
||||
/// [enum variants]
|
||||
// used by crate::inlib
|
||||
#[allow(unused)]
|
||||
pub type LibInitInfo = Vec<(String, usize)>;
|
||||
pub type LibInitInfoRef<'a> = Vec<(&'a String, &'a usize)>;
|
||||
|
||||
impl Lib {
|
||||
pub fn launch(
|
||||
mut exec: Command,
|
||||
enum_variants: &mut HashMap<String, usize>,
|
||||
) -> Result<Self, LaunchError> {
|
||||
let mut handle = match exec
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(LaunchError::CouldNotSpawnProcess(e)),
|
||||
};
|
||||
if let (Some(mut stdin), Some(stdout)) = (handle.stdin.take(), handle.stdout.take()) {
|
||||
let mut stdout = BufReader::new(stdout);
|
||||
let comms_version: u128 = ByteData::from_byte_data(&mut stdout).unwrap();
|
||||
assert_eq!(comms_version, 1);
|
||||
let (ver_major, ver_minor, name, description, mut registered_fns) =
|
||||
LibInitReq::from_byte_data(&mut stdout).unwrap();
|
||||
eprintln!("- <<< ADDING LIB: {name} v{ver_major}.{ver_minor} >>> -");
|
||||
for line in description.lines() {
|
||||
eprintln!(" | {line}");
|
||||
}
|
||||
for (name, _) in registered_fns.iter() {
|
||||
eprintln!(" fn {name}");
|
||||
}
|
||||
let mut ginfo = GlobalScriptInfo::default();
|
||||
for (_name, func) in registered_fns.iter_mut() {
|
||||
for (args, out) in func.iter_mut() {
|
||||
for t in args.iter_mut() {
|
||||
if let Err(e) = crate::lang::to_runnable::stypes(t, &mut ginfo) {
|
||||
return Err(LaunchError::ErrorAddingEnumsOrTypes(e));
|
||||
}
|
||||
}
|
||||
if let Err(e) = crate::lang::to_runnable::stypes(out, &mut ginfo) {
|
||||
return Err(LaunchError::ErrorAddingEnumsOrTypes(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name, id) in ginfo.enum_variants {
|
||||
if !enum_variants.contains_key(&name) {
|
||||
enum_variants.insert(name, id);
|
||||
}
|
||||
}
|
||||
let si: LibInitInfoRef = enum_variants.iter().collect();
|
||||
if let Err(e) = stdin.write_all(si.as_byte_data_vec().as_slice()) {
|
||||
return Err(LaunchError::StdioError(e));
|
||||
};
|
||||
if let Err(e) = stdin.flush() {
|
||||
return Err(LaunchError::StdioError(e));
|
||||
};
|
||||
let (task_sender, recv) = std::sync::mpsc::channel::<(
|
||||
u128,
|
||||
Box<dyn FnOnce(&mut BufReader<ChildStdout>) + Send>,
|
||||
)>();
|
||||
let _stdout_reader = std::thread::spawn(move || {
|
||||
let dur = std::time::Duration::from_millis(20);
|
||||
let mut pending = HashMap::new();
|
||||
loop {
|
||||
// read id from stdout
|
||||
if let Ok(id) = u128::from_byte_data(&mut stdout) {
|
||||
// update pending
|
||||
loop {
|
||||
if let Ok((id, sender)) = recv.try_recv() {
|
||||
pending.insert(id, sender);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// find task with that id
|
||||
if let Some(sender) = pending.remove(&id) {
|
||||
// call the callback function, which will handle the rest
|
||||
sender(&mut stdout)
|
||||
} else {
|
||||
eprintln!("ID {id} not found! possible decode/encode error?");
|
||||
}
|
||||
std::thread::sleep(dur);
|
||||
} else {
|
||||
eprintln!(
|
||||
"Library has exited, tasks pending: {}",
|
||||
pending.iter().enumerate().fold(
|
||||
String::new(),
|
||||
|mut s, (i, (id, _))| if i == 0 {
|
||||
format!("{id}")
|
||||
} else {
|
||||
s.push_str(format!(", {id}").as_str());
|
||||
s
|
||||
}
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(Self {
|
||||
name,
|
||||
process: handle,
|
||||
stdin: Arc::new(Mutex::new(stdin)),
|
||||
// stdout: Arc::new(Mutex::new(stdout)),
|
||||
task_sender: Arc::new(Mutex::new(task_sender)),
|
||||
current_id: Arc::new(Mutex::new(0)),
|
||||
registered_fns,
|
||||
})
|
||||
} else {
|
||||
return Err(LaunchError::NoStdio);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_fn(&self, fnid: usize, args: Vec<VData>) -> VData {
|
||||
self.get_response(comms::run_function::Message {
|
||||
function_id: fnid as _,
|
||||
args,
|
||||
})
|
||||
.result
|
||||
}
|
||||
fn get_response<M>(&self, msg: M) -> M::Response
|
||||
where
|
||||
M: RespondableMessage,
|
||||
<M as comms::RespondableMessage>::Response: Send + 'static,
|
||||
{
|
||||
let recv = {
|
||||
let mut id = self.current_id.lock().unwrap();
|
||||
let mut stdin = self.stdin.lock().unwrap();
|
||||
let (sender, recv) = std::sync::mpsc::sync_channel(2);
|
||||
self.task_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send((
|
||||
*id,
|
||||
Box::new(move |stdout| {
|
||||
sender
|
||||
.send(ByteData::from_byte_data(stdout).unwrap())
|
||||
.unwrap();
|
||||
}),
|
||||
))
|
||||
.unwrap();
|
||||
// id - type_id - message
|
||||
stdin.write_all(id.as_byte_data_vec().as_slice()).unwrap();
|
||||
stdin
|
||||
.write_all(msg.msgtype_id().as_byte_data_vec().as_slice())
|
||||
.unwrap();
|
||||
stdin.write_all(msg.as_byte_data_vec().as_slice()).unwrap();
|
||||
stdin.flush().unwrap();
|
||||
*id = id.wrapping_add(1);
|
||||
recv
|
||||
};
|
||||
recv.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LaunchError {
|
||||
NoStdio,
|
||||
CouldNotSpawnProcess(io::Error),
|
||||
StdioError(io::Error),
|
||||
ErrorAddingEnumsOrTypes(ToRunnableError),
|
||||
}
|
||||
impl std::fmt::Display for LaunchError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NoStdio => write!(f, "couldn't get stdio (stdin/stdout) from child process."),
|
||||
Self::CouldNotSpawnProcess(e) => write!(f, "couldn't spawn child process: {e}."),
|
||||
Self::StdioError(e) => write!(f, "error from stdio: {e}"),
|
||||
Self::ErrorAddingEnumsOrTypes(e) => {
|
||||
write!(f, "error adding enums or types from library: {e}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
288
mers/src/main.rs
288
mers/src/main.rs
@@ -1,245 +1,53 @@
|
||||
use lang::global_info::ColorFormatMode;
|
||||
use lang::global_info::GlobalScriptInfo;
|
||||
use lang::global_info::LogKind;
|
||||
use lang::val_data::VDataEnum;
|
||||
use lang::val_type::VSingleType;
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use mers_lib::prelude_compile::*;
|
||||
use std::{fmt::Display, fs, path::PathBuf};
|
||||
|
||||
use crate::lang::fmtgs::FormatGs;
|
||||
|
||||
mod interactive_mode;
|
||||
mod lang;
|
||||
mod libs;
|
||||
#[cfg(feature = "nushell_plugin")]
|
||||
mod nushell_plugin;
|
||||
mod parsing;
|
||||
mod pathutil;
|
||||
mod tutor;
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(feature = "nushell_plugin"))]
|
||||
normal_main();
|
||||
#[cfg(feature = "nushell_plugin")]
|
||||
nushell_plugin::main();
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[arg(long, value_enum, default_value_t = Configs::Std)]
|
||||
config: Configs,
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
fn normal_main() {
|
||||
let args: Vec<_> = std::env::args().skip(1).collect();
|
||||
let mut info = GlobalScriptInfo::default();
|
||||
let mut run = true;
|
||||
let mut args_to_skip = 2;
|
||||
let mut file = match args.len() {
|
||||
0 => {
|
||||
println!("no arguments, use -h for help");
|
||||
std::process::exit(100);
|
||||
}
|
||||
_ => {
|
||||
if args[0].trim_start().starts_with("-") {
|
||||
let mut execute = false;
|
||||
let mut print_version = false;
|
||||
let mut verbose = false;
|
||||
let mut verbose_args = String::new();
|
||||
let mut interactive = 0;
|
||||
let mut interactive_use_new_terminal = false;
|
||||
let mut teachme = false;
|
||||
let mut prev_char = None;
|
||||
let mut advanced = false;
|
||||
for ch in args[0][1..].chars() {
|
||||
if !advanced {
|
||||
if ch == '+' {
|
||||
advanced = true;
|
||||
continue;
|
||||
}
|
||||
match ch {
|
||||
'h' => {
|
||||
eprintln!("~~~~ mers help ~~~~");
|
||||
eprintln!();
|
||||
eprintln!(" ~~ cli ~~");
|
||||
eprintln!("Mers has the following cli options:");
|
||||
eprintln!("-h shows this Help message");
|
||||
eprintln!("-e - mers will treat the run argument as code to be Executed rather than a file path");
|
||||
eprintln!(" mers -e 'println(\"Hello, World!\")'");
|
||||
eprintln!(
|
||||
"-c - mers will Check the code for errors, but won't run it"
|
||||
);
|
||||
eprintln!("-f - mers will Format the code and print it. useful if you suspect the parser might be misinterpreting your code");
|
||||
eprintln!(
|
||||
"+c - use Colors in the output to better visualize things"
|
||||
);
|
||||
eprintln!("+C - don't use colors (opposite of +c, redundant since this is the default)");
|
||||
eprintln!("-v - mers will be more Verbose");
|
||||
eprintln!("+???+ - customize what mers is verbose about and how - bad syntax, barely useful, don't use it until it gets improved (TODO!)");
|
||||
eprintln!("-i - launches an Interactive session to play around with (opens your editor and runs code on each file save)");
|
||||
eprintln!("+t - spawns a new terminal for the editor (if you use a terminal editors, add +t)");
|
||||
eprintln!(" mers -i+t");
|
||||
eprintln!("-t - launches the Tutor, which will attempt to Teach you the basics of the language");
|
||||
eprintln!();
|
||||
eprintln!(" ~~ getting started ~~");
|
||||
eprintln!("mers doesn't need a specific structure for directories, just create a UTF-8 text file, write code, and run it:");
|
||||
eprintln!(" echo 'println(\"Hello, World!\")' > hello.mers");
|
||||
eprintln!(" mers hello.mers");
|
||||
return;
|
||||
}
|
||||
'e' => execute = true,
|
||||
'v' => verbose = true,
|
||||
'c' => run = false,
|
||||
'f' => {
|
||||
run = false;
|
||||
info.log.after_parse.stderr = true;
|
||||
}
|
||||
'V' => print_version = true,
|
||||
'i' => interactive += 1,
|
||||
't' => teachme = true,
|
||||
ch => {
|
||||
eprintln!("Ignoring -{ch}. (unknown char)");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev_char = Some(ch);
|
||||
} else {
|
||||
advanced = false;
|
||||
if let Some(prev_char) = prev_char {
|
||||
match prev_char {
|
||||
'i' => match ch {
|
||||
't' => interactive_use_new_terminal = true,
|
||||
_ => eprintln!("Ignoring i+{ch}. (unknown adv char)"),
|
||||
},
|
||||
'v' => {
|
||||
if ch != '+' {
|
||||
advanced = true;
|
||||
verbose_args.push(ch);
|
||||
}
|
||||
}
|
||||
'f' => match ch {
|
||||
'c' => info.formatter.mode = ColorFormatMode::Colorize,
|
||||
'C' => info.formatter.mode = ColorFormatMode::Plain,
|
||||
_ => eprintln!("Ignoring f+{ch}. (unknown adv char)"),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"Ignoring advanced args because there was no previous argument."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if print_version {
|
||||
println!(
|
||||
"mers {}",
|
||||
option_env!("CARGO_PKG_VERSION")
|
||||
.unwrap_or("[[ version unknown: no CARGO_PKG_VERSION ]]")
|
||||
);
|
||||
return;
|
||||
}
|
||||
if teachme {
|
||||
tutor::start(false);
|
||||
return;
|
||||
}
|
||||
if verbose {
|
||||
if verbose_args.is_empty() {
|
||||
fn f() -> LogKind {
|
||||
LogKind {
|
||||
stderr: true,
|
||||
log: true,
|
||||
}
|
||||
}
|
||||
info.log.vtype_fits_in = f();
|
||||
info.log.vsingletype_fits_in = f();
|
||||
} else {
|
||||
fn kind(val: Option<&str>) -> LogKind {
|
||||
match val {
|
||||
Some("stderr") => LogKind {
|
||||
stderr: true,
|
||||
..Default::default()
|
||||
},
|
||||
Some("log") => LogKind {
|
||||
log: true,
|
||||
..Default::default()
|
||||
},
|
||||
Some("log+stderr" | "stderr+log") => LogKind {
|
||||
stderr: true,
|
||||
log: true,
|
||||
..Default::default()
|
||||
},
|
||||
_ => LogKind {
|
||||
stderr: true,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
for verbose_arg in verbose_args.split(',') {
|
||||
let (arg, val) = match verbose_arg.split_once('=') {
|
||||
Some((left, right)) => (left, Some(right)),
|
||||
None => (verbose_arg, None),
|
||||
};
|
||||
match arg {
|
||||
"vtype_fits_in" => info.log.vtype_fits_in = kind(val),
|
||||
"vsingletype_fits_in" => info.log.vsingletype_fits_in = kind(val),
|
||||
_ => eprintln!("Warn: -v+ unknown arg '{arg}'."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if interactive > 0 {
|
||||
match interactive {
|
||||
_ => {
|
||||
// basic: open file and watch for fs changes
|
||||
interactive_mode::fs_watcher::playground(interactive_use_new_terminal)
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
return;
|
||||
} else if execute {
|
||||
parsing::file::File::new(
|
||||
args.iter().skip(1).fold(String::new(), |mut s, v| {
|
||||
if !s.is_empty() {
|
||||
s.push(' ');
|
||||
}
|
||||
s.push_str(v);
|
||||
s
|
||||
}),
|
||||
std::path::PathBuf::new(),
|
||||
)
|
||||
} else {
|
||||
args_to_skip += 1;
|
||||
if let Some(file) = args.get(1) {
|
||||
parsing::file::File::new(
|
||||
std::fs::read_to_string(file).unwrap(),
|
||||
file.into(),
|
||||
)
|
||||
} else {
|
||||
println!("nothing to do - missing arguments?");
|
||||
std::process::exit(101);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parsing::file::File::new(
|
||||
std::fs::read_to_string(&args[0]).unwrap(),
|
||||
args[0].as_str().into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
info.main_fn_args = vec![(
|
||||
"args".to_string(),
|
||||
VSingleType::List(VSingleType::String.into()).to(),
|
||||
)];
|
||||
match parsing::parse::parse_custom_info(&mut file, info) {
|
||||
Ok(script) => {
|
||||
if run {
|
||||
script.run(vec![VDataEnum::List(
|
||||
VSingleType::String.to(),
|
||||
std::env::args()
|
||||
.skip(args_to_skip)
|
||||
.map(|v| VDataEnum::String(v).to())
|
||||
.collect(),
|
||||
)
|
||||
.to()]);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Couldn't compile:\n{}", e.with_file(&file));
|
||||
std::process::exit(99);
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Run { file: PathBuf },
|
||||
Exec { source: String },
|
||||
}
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum Configs {
|
||||
None,
|
||||
Std,
|
||||
}
|
||||
impl Display for Configs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "none"),
|
||||
Self::Std => write!(f, "std"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
let config = match args.config {
|
||||
Configs::None => Config::new(),
|
||||
Configs::Std => Config::new().bundle_std(),
|
||||
};
|
||||
let (mut info1, mut info2) = config.infos();
|
||||
match args.command {
|
||||
Command::Run { file } => {
|
||||
let str = fs::read_to_string(file).unwrap();
|
||||
let mut src = Source::new(str);
|
||||
let parsed = parse(&mut src).unwrap();
|
||||
let run = parsed.compile(&mut info1, Default::default()).unwrap();
|
||||
run.run(&mut info2);
|
||||
}
|
||||
Command::Exec { source } => {
|
||||
let mut src = Source::new(source);
|
||||
let parsed = parse(&mut src).unwrap();
|
||||
let run = parsed.compile(&mut info1, Default::default()).unwrap();
|
||||
run.run(&mut info2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin};
|
||||
use nu_protocol::{PluginSignature, ShellError, Span, SyntaxShape, Value};
|
||||
|
||||
use crate::{
|
||||
lang::{
|
||||
fmtgs::FormatGs,
|
||||
global_info::GlobalScriptInfo,
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::VType,
|
||||
},
|
||||
parsing,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
serve_plugin(&mut MersNuPlugin(), MsgPackSerializer {});
|
||||
}
|
||||
|
||||
struct MersNuPlugin();
|
||||
|
||||
impl Plugin for MersNuPlugin {
|
||||
fn signature(&self) -> Vec<nu_protocol::PluginSignature> {
|
||||
vec![PluginSignature::build("mers-nu")
|
||||
.required(
|
||||
"mers",
|
||||
SyntaxShape::String,
|
||||
"the path to the .mers file to run or the mers source code if -e is set",
|
||||
)
|
||||
.optional(
|
||||
"args",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
SyntaxShape::List(Box::new(SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Boolean,
|
||||
SyntaxShape::Int,
|
||||
SyntaxShape::Decimal,
|
||||
SyntaxShape::String,
|
||||
]))),
|
||||
]))),
|
||||
]))),
|
||||
"the arguments passed to the mers program. defaults to an empty list.",
|
||||
)
|
||||
.switch(
|
||||
"execute",
|
||||
"instead of reading from a file, interpret the 'mers' input as source code",
|
||||
Some('e'),
|
||||
)]
|
||||
}
|
||||
fn run(
|
||||
&mut self,
|
||||
_name: &str,
|
||||
call: &nu_plugin::EvaluatedCall,
|
||||
_input: &nu_protocol::Value,
|
||||
) -> Result<nu_protocol::Value, nu_plugin::LabeledError> {
|
||||
// no need to 'match name {...}' because we only register mers-nu and nothing else.
|
||||
let source: String = call.req(0)?;
|
||||
let source_span = Span::unknown(); // source.span;
|
||||
// let source = source.item;
|
||||
let mut file = if call.has_flag("execute") {
|
||||
parsing::file::File::new(source, PathBuf::new())
|
||||
} else {
|
||||
parsing::file::File::new(
|
||||
match fs::read_to_string(&source) {
|
||||
Ok(v) => v,
|
||||
Err(_e) => {
|
||||
return Ok(Value::Error {
|
||||
error: Box::new(ShellError::FileNotFound(source_span)),
|
||||
})
|
||||
}
|
||||
},
|
||||
source.into(),
|
||||
)
|
||||
};
|
||||
Ok(match parsing::parse::parse(&mut file) {
|
||||
Ok(code) => {
|
||||
let args = match call.opt(1)? {
|
||||
Some(v) => {
|
||||
fn to_mers_val(v: Vec<Value>, info: &GlobalScriptInfo) -> Vec<VData> {
|
||||
v.into_iter()
|
||||
.map(|v| {
|
||||
match v {
|
||||
Value::Bool { val, .. } => VDataEnum::Bool(val),
|
||||
Value::Int { val, .. } => VDataEnum::Int(val as _),
|
||||
Value::Float { val, .. } => VDataEnum::Float(val),
|
||||
Value::String { val, .. } => VDataEnum::String(val),
|
||||
Value::List { vals, .. } => {
|
||||
let mut t = VType::empty();
|
||||
let mut vs = Vec::with_capacity(vals.len());
|
||||
for v in to_mers_val(vals, info) {
|
||||
t.add_types(v.out(), info);
|
||||
vs.push(v);
|
||||
}
|
||||
VDataEnum::List(t, vs)
|
||||
}
|
||||
_ => unreachable!("invalid arg type"),
|
||||
}
|
||||
.to()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
if let Value::List { vals, .. } = v {
|
||||
to_mers_val(vals, &code.info)
|
||||
} else {
|
||||
unreachable!("args not a list")
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
fn to_nu_val(val: &VData, info: &GlobalScriptInfo) -> Value {
|
||||
let span = Span::unknown();
|
||||
val.operate_on_data_immut(|val| match val {
|
||||
VDataEnum::Bool(val) => Value::Bool { val: *val, span },
|
||||
VDataEnum::Int(val) => Value::Int {
|
||||
val: *val as _,
|
||||
span,
|
||||
},
|
||||
VDataEnum::Float(val) => Value::Float { val: *val, span },
|
||||
VDataEnum::String(val) => Value::String {
|
||||
val: val.to_owned(),
|
||||
span,
|
||||
},
|
||||
VDataEnum::Tuple(vals) | VDataEnum::List(_, vals) => Value::List {
|
||||
vals: vals.iter().map(|v| to_nu_val(v, info)).collect(),
|
||||
span,
|
||||
},
|
||||
VDataEnum::Reference(r) => to_nu_val(r, info),
|
||||
VDataEnum::EnumVariant(variant, val) => {
|
||||
let name = info
|
||||
.enum_variants
|
||||
.iter()
|
||||
.find_map(|(name, id)| {
|
||||
if *id == *variant {
|
||||
Some(name.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Value::Record {
|
||||
cols: vec![format!("Enum"), format!("Value")],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: name,
|
||||
span: span,
|
||||
},
|
||||
to_nu_val(val, info),
|
||||
],
|
||||
span,
|
||||
}
|
||||
}
|
||||
VDataEnum::Function(_func) => Value::Nothing { span },
|
||||
VDataEnum::Thread(t, _) => to_nu_val(&t.get(), info),
|
||||
})
|
||||
}
|
||||
to_nu_val(&code.run(args), &code.info)
|
||||
}
|
||||
Err(e) => Value::Error {
|
||||
error: Box::new(ShellError::IncorrectValue {
|
||||
msg: format!("Couldn't compile mers, error: {}", e.with_file(&file)),
|
||||
span: source_span,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Index, Range, RangeFrom, RangeTo},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub struct File {
|
||||
path: PathBuf,
|
||||
data: String,
|
||||
chars: Vec<(usize, char)>,
|
||||
// contains the byte indices of all newline characters
|
||||
newlines: Vec<usize>,
|
||||
pos: FilePosition,
|
||||
ppos: FilePosition,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePosition {
|
||||
pub current_char_index: usize,
|
||||
pub current_line: usize,
|
||||
pub current_column: usize,
|
||||
}
|
||||
impl Display for FilePosition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"line {}, col. {}",
|
||||
self.current_line + 1,
|
||||
self.current_column + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// creates a file from its contents and its path. Path can be PathBuf::new(), but this disables relative MersLibs.
|
||||
pub fn new(data: String, path: PathBuf) -> Self {
|
||||
let data = if data.starts_with("#!") {
|
||||
&data[data.lines().next().unwrap().len()..].trim_start()
|
||||
} else {
|
||||
data.trim_start()
|
||||
};
|
||||
let mut chs = data.chars();
|
||||
let mut data = String::with_capacity(data.len());
|
||||
loop {
|
||||
match chs.next() {
|
||||
Some('\\') => match chs.next() {
|
||||
// backslash can escape these characters:
|
||||
Some('\n') => data.push('\\'),
|
||||
// backshash invalidates comments, so \// will just be //.
|
||||
Some('/') => data.push('/'),
|
||||
// backslash does nothing otherwise.
|
||||
Some(ch) => {
|
||||
data.push('\\');
|
||||
data.push(ch);
|
||||
}
|
||||
None => data.push('\\'),
|
||||
},
|
||||
Some('/') => match chs.next() {
|
||||
Some('/') => loop {
|
||||
match chs.next() {
|
||||
Some('\n') => {
|
||||
data.push('\n');
|
||||
break;
|
||||
}
|
||||
None => break,
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Some('*') => loop {
|
||||
match chs.next() {
|
||||
Some('*') => {
|
||||
if let Some('/') = chs.next() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
Some(ch) => {
|
||||
data.push('/');
|
||||
data.push(ch);
|
||||
}
|
||||
None => {
|
||||
data.push('/');
|
||||
break;
|
||||
}
|
||||
},
|
||||
Some(ch) => data.push(ch),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
if !data.ends_with('\n') {
|
||||
data.push('\n');
|
||||
}
|
||||
let chars: Vec<_> = data.char_indices().collect();
|
||||
let newlines: Vec<_> = chars
|
||||
.iter()
|
||||
.filter_map(|v| if v.1 == '\n' { Some(v.0) } else { None })
|
||||
.collect();
|
||||
let pos = FilePosition {
|
||||
current_char_index: 0,
|
||||
current_line: 0,
|
||||
current_column: 0,
|
||||
};
|
||||
Self {
|
||||
path,
|
||||
data,
|
||||
chars,
|
||||
newlines,
|
||||
pos,
|
||||
ppos: pos,
|
||||
}
|
||||
}
|
||||
pub fn skip_whitespaces(&mut self) {
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(ch) if ch.is_whitespace() => _ = self.next(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn collect_to_whitespace(&mut self) -> String {
|
||||
let mut o = String::new();
|
||||
loop {
|
||||
if let Some(ch) = self.next() {
|
||||
if ch.is_whitespace() {
|
||||
self.set_pos(*self.get_ppos());
|
||||
break;
|
||||
}
|
||||
o.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
pub fn get_pos(&self) -> &FilePosition {
|
||||
&self.pos
|
||||
}
|
||||
pub fn get_ppos(&self) -> &FilePosition {
|
||||
&self.ppos
|
||||
}
|
||||
pub fn set_pos(&mut self, pos: FilePosition) {
|
||||
self.pos = pos;
|
||||
}
|
||||
pub fn get_char(&self, index: usize) -> Option<char> {
|
||||
match self.chars.get(index) {
|
||||
Some(v) => Some(v.1),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn get_line(&self, line_nr: usize) -> Option<&str> {
|
||||
if self.newlines.len() > line_nr {
|
||||
Some(if line_nr == 0 {
|
||||
&self.data[0..self.newlines[0]]
|
||||
} else {
|
||||
&self.data[self.newlines[line_nr - 1] + 1..self.newlines[line_nr]]
|
||||
})
|
||||
} else if self.newlines.len() == line_nr {
|
||||
Some(if line_nr == 0 {
|
||||
self.data.as_str()
|
||||
} else {
|
||||
&self.data[self.newlines[line_nr - 1] + 1..]
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// returns the lines. both from and to are inclusive.
|
||||
pub fn get_lines(&self, from: usize, to: usize) -> Option<&str> {
|
||||
let start_index = if from == 0 {
|
||||
0
|
||||
} else if from <= self.newlines.len() {
|
||||
self.newlines[from - 1] + 1
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let end_index = if to == self.newlines.len() {
|
||||
self.data.len()
|
||||
} else if to < self.newlines.len() {
|
||||
self.newlines[to]
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(&self.data[start_index..end_index])
|
||||
}
|
||||
pub fn next_line(&mut self) -> String {
|
||||
let mut o = String::new();
|
||||
for ch in self {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
} else {
|
||||
o.push(ch);
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn peek(&self) -> Option<char> {
|
||||
match self.chars.get(self.pos.current_char_index) {
|
||||
Some((_, c)) => Some(*c),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for File {
|
||||
type Item = char;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.ppos = self.pos;
|
||||
let o = self.chars.get(self.pos.current_char_index);
|
||||
self.pos.current_char_index += 1;
|
||||
match o {
|
||||
Some((_, ch)) => {
|
||||
match *ch {
|
||||
'\n' => {
|
||||
self.pos.current_line += 1;
|
||||
self.pos.current_column = 0;
|
||||
}
|
||||
_ => self.pos.current_column += 1,
|
||||
}
|
||||
// #[cfg(debug_assertions)]
|
||||
// eprint!("{ch}");
|
||||
Some(*ch)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<Range<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: Range<usize>) -> &Self::Output {
|
||||
if let Some((start, _)) = self.chars.get(index.start) {
|
||||
if let Some((end, _)) = self.chars.get(index.end) {
|
||||
&self.data[*start..*end]
|
||||
} else {
|
||||
&self.data[*start..]
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Index<RangeFrom<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
|
||||
if let Some((start, _)) = self.chars.get(index.start) {
|
||||
&self.data[*start..]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Index<RangeTo<usize>> for File {
|
||||
type Output = str;
|
||||
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
|
||||
if let Some((end, _)) = self.chars.get(index.end) {
|
||||
&self.data[..*end]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod file;
|
||||
pub mod parse;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,35 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn path_from_string(
|
||||
path: &str,
|
||||
script_path: &PathBuf,
|
||||
fallback_to_lib_dir: bool,
|
||||
) -> Option<PathBuf> {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_absolute() {
|
||||
return Some(path);
|
||||
}
|
||||
if let Some(p) = script_path
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| script_path.clone())
|
||||
.parent()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: parent path: {p:?}");
|
||||
let p = p.join(&path);
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: joined: {p:?}");
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
if fallback_to_lib_dir {
|
||||
if let Ok(mers_lib_dir) = std::env::var("MERS_LIB_DIR") {
|
||||
let p = PathBuf::from(mers_lib_dir).join(&path);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Comments in mers start at // and end at the end of the line.
|
||||
// They also work within strings, which can be unexpected in some cases (like \"http://www...\").
|
||||
/* also works to start a comment.
|
||||
This comment can even span multiple lines! */
|
||||
// To return to the menu, uncomment the next line:
|
||||
// true
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Bool(true) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {} instead of true.", other));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Functions represent certain actions.
|
||||
// They are given some inputs (arguments) and output (return) something.
|
||||
// Mers comes with a range of builtin functions defined in src/script/builtins.rs.
|
||||
|
||||
// As an example, let's look at the add() function:
|
||||
// It takes two arguments as its input and adds them together, then returns the sum:
|
||||
add(5 10) // 15
|
||||
// Similar to this, sub() subtracts two numbers:
|
||||
sub(15 5) // 10
|
||||
|
||||
// For some functions, there is no value they could return:
|
||||
sleep(0.01) // wait 0.01 seconds, then continue.
|
||||
// These will return an empty tuple [] in mers.
|
||||
|
||||
// However, you aren't limited to the builtin functions.
|
||||
// You can easily define your own functions to do more complex tasks:
|
||||
fn say_hello_world() {
|
||||
println(\"Hello, world!\")
|
||||
}
|
||||
|
||||
// Since the Subject.Verb(Object) syntax is more natural to many people, a.function(b c d) is an alternative way of writing function(a b c d):
|
||||
my_var = 15
|
||||
format(\"my variable had the value {0}!\" my_var) // normal
|
||||
\"my variable had the value {0}!\".format(my_var) // alternative (does the same thing)
|
||||
|
||||
// to return to the menu, add two arguments to the mul() function to make it return 32*5
|
||||
mul()
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(160) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of 160"));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Mers doesn't have a return statement.
|
||||
// Instead, the value of the last statement is implicitly returned.
|
||||
|
||||
// This applies to blocks:
|
||||
b = {
|
||||
a = 10
|
||||
a = a.add(15)
|
||||
a
|
||||
}
|
||||
// b = 25
|
||||
|
||||
// To functions:
|
||||
fn compute_sum(a int b int) {
|
||||
a.add(b)
|
||||
}
|
||||
// returns a+b
|
||||
|
||||
// and to the program itself!
|
||||
// to return to the menu, make the program return 15.
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(15) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {} instead of 15.", other));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Mers uses a type system to verify your programs,
|
||||
// which prevents your program from crashing.
|
||||
// Mers will verify that your program is valid and will not run into issues before it is executed.
|
||||
// This way, errors are found when you write the program, not when you run it. If mers runs your program
|
||||
// it is almost always safe to assume that it will not crash.
|
||||
|
||||
// for example, this will cause an error because you cannot subtract text from numbers.
|
||||
// sub(15 \"some text\")
|
||||
|
||||
// mers can verify this type-safety in all programs, no matter how complicated they are:
|
||||
// a = 15 // mers knows: a is an int
|
||||
// b = \"some text\" // mers knows: b is a string
|
||||
// sub(a b) // mers knows: it can't subtract a string from an int
|
||||
|
||||
// Just like other statically-typed languages, mers achieves this safety by assigning a certain type to each variable (technically to each statement).
|
||||
// However, mers' type-system has one quirk that sets it apart from most others:
|
||||
|
||||
a = if true {
|
||||
\"some string\"
|
||||
} else {
|
||||
12
|
||||
}
|
||||
switch! a {}
|
||||
|
||||
// A type in mers can consist of multiple single types: The type of a is 'string/int', because it could be either a string or an int.
|
||||
// You can see this type mentioned in the error at the top of the file, which shows up because 'switch!' wants us to handle all possible types,
|
||||
// yet we don't handle any ('{}').
|
||||
|
||||
// By combining tuples ('[a b c]') with the idea of multiple-types, you can create complex datastructures.
|
||||
// You effectively have all the power of Rust enums (enums containing values) and structs combined:
|
||||
// Rust's Option<T>: t/[] or [t]/[] if t can be [] itself
|
||||
// Rust's Result<T, E>: T/Err(E)
|
||||
|
||||
// The Err(E) is mers' version of an enum. An enum in mers is an identifier ('Err') wrapping a type ('E').
|
||||
// They don't need to be declared anywhere. You can just return 'PossiblyWrongValue: 0.33' from your function and mers will handle the rest.
|
||||
// To access the inner value, you can use the noenum() function:
|
||||
// result = SomeValueInAnEnum: \"a string\"
|
||||
// println(result) // error - result is not a string
|
||||
// println(result.noenum()) // works because result is an enum containing a string
|
||||
|
||||
// the \\S+ regex matches anything but whitespaces
|
||||
words_in_string = \"some string\".regex(\"\\\\S+\")
|
||||
switch! words_in_string {}
|
||||
// Types to cover: [string ...]/Err(string) - If the regex is invalid, regex() will return an error.
|
||||
|
||||
// To return to the menu, fix all compiler errors (comment out all switch! statements).
|
||||
|
||||
true
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Tuple(v) if v.is_empty() => {
|
||||
tutor.set_status(format!(" - Returned an empty tuple."));
|
||||
tutor.update(None);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Mers has the following values:
|
||||
// bool: Either true or false
|
||||
// int: An integer. Written 0, 1, 2, 3, -5, and so on
|
||||
// float: A floating point number. Written 0.5, -12.378, and so on
|
||||
// string: A piece of text. Surround something with \" and it will be a string: \"Hello, world!\"
|
||||
// tuples: Multiple values in one place. A fixed-length collection. Surround types or statements with [] to create a tuple: [12 \"a tuple of ints and a string\" -5 -12]
|
||||
// The empty tuple [] is often used to indicate nothing, while a 1-long tuple [v] indicates the opposite - something.
|
||||
// list: Similar to tuples, but the closing ] is prefixed with 3 dots: [ ...]
|
||||
// Unlike tuples, all elements in a list have the same type. Lists are resizable and can grow dynamically, while tuples cannot change their size after being created.
|
||||
// function: A piece of code in data-form.
|
||||
// value: anonymous_sum_function = (a int/float b int/float) a.add(b)
|
||||
// type: fn((int int int)(int float float)(float int float)(float float float))
|
||||
// the reason why the type syntax is so expressive is because the function doesn't return the same type for any inputs - add will return an int if it added two ints, but will return a float when at least one argument was a float.
|
||||
// add will NOT return int/float, because if you know the exact input types, you also know the output type: either int and not float or float and not int.
|
||||
// thread: Represents a different thread. The thread's return value can be retrieved by using .await(). Thread values are returned by the builtin thread() function.
|
||||
// reference: A mutable reference to some data. Used by things like push() and remove() to avoid having to clone the entire list just to make a small change.
|
||||
// enums: An enum can wrap any type. Enums are identified by their names and can be created using EnumName: inner_value. The type is written EnumName(InnerType).
|
||||
// return any enum to return to the menu.
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::EnumVariant(..) => break,
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of an enum."));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some(
|
||||
"
|
||||
// A variable can be used to store values.
|
||||
// Create one by assigning a value to it:
|
||||
my_first_variable = 15
|
||||
// Then use it instead of literal values:
|
||||
five_less = sub(my_first_variable 5) // 10
|
||||
|
||||
// to return to the menu, create a variable my_name and assign your name to it.
|
||||
|
||||
|
||||
/* return the name so the tutor can check it - ignore this */ my_name
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::String(name) if !name.is_empty() => {
|
||||
tutor.i_name = Some(name.to_owned());
|
||||
break;
|
||||
}
|
||||
VDataEnum::String(_) => {
|
||||
tutor.set_status(format!(" - Almost there, you made an empty string. Put your name between the quotes to continue!"));
|
||||
tutor.update(None);
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(" - Returned {other} instead of a string. String literals start and end with double quotes (\")."));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub fn run(tutor: &mut Tutor) {
|
||||
tutor.update(Some("
|
||||
// Error handling in mers is inspired by Rust. Errors aren't a language feature,
|
||||
// they are just a value like any other. Usually, errors have the type Err(string) or Err(SomeOtherEnum(string)),
|
||||
// but this is still just a value in an enum.
|
||||
// Because of the type system in mers, errors can just be used and don't need any special language features.
|
||||
// This part of the mers-tutor isn't as much about error handling as it is about dealing with multiple types when you only want some of them, not all,
|
||||
// but since error handling is the main use-case for this, it felt like the better title for this section.
|
||||
|
||||
// 1. [t]/[]
|
||||
// This acts like null/nil in most languages or Option<T> in rust.
|
||||
// This type indicates either '[]', a tuple of length 0 and therefore no data (null/nil/None)
|
||||
// or '[t]', a tuple of length 1 - one value. This has to be [t] and not just t because t might be [], which would otherwise cause ambiguity.
|
||||
|
||||
// The type [t]/[] is returned by get(), a function that retrieves an item from a list and returns [] if the index was less than 0 or too big for the given list:
|
||||
list = [1 2 3 4 5 ...]
|
||||
first = list.get(0) // = [1]
|
||||
second = list.get(1) // = [2]
|
||||
does_not_exist = list.get(9) // = []
|
||||
|
||||
// To handle the result from get(), we can switch on the type:
|
||||
switch! first {
|
||||
[int] \"First element in the list: {0}\".format(first.0.to_string())
|
||||
[] \"List was empty!\"
|
||||
}
|
||||
|
||||
// If we already know that the list isn't empty, we can use assume1(). This function takes a [t]/[] and returns t. If it gets called with [], it will crash your program.
|
||||
\"First element in the list: {0}\".format(first.assume1().to_string())
|
||||
|
||||
// 2. t/Err(e)
|
||||
// This acts like Rust's Result<T, E> and is used in error-handling.
|
||||
// This is mainly used by functions that do I/O (fs_* and run_command) and can also be handeled using switch or switch! statements.
|
||||
// Use switch! or .debug() to see the types returned by these functions in detail.
|
||||
// If switching is too much effort for you and you would like to just crash the program on any error,
|
||||
// you can use assume_no_enum() to ignore all enum types:
|
||||
// - t/Err(e) becomes t
|
||||
// - int/float/string/Err(e)/Err(a) becomes int/float/string
|
||||
|
||||
// To return to the menu, change the index in list.get() so that it returns a value of type [int] instead of [].
|
||||
list.get(8)
|
||||
"));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Tuple(v) if !v.is_empty() => {
|
||||
break;
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(
|
||||
" - Returned {other} instead of a value of type [int]."
|
||||
));
|
||||
tutor.update(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
use crate::lang::val_data::VDataEnum;
|
||||
|
||||
use super::Tutor;
|
||||
|
||||
pub const MAX_POS: usize = 7;
|
||||
|
||||
pub fn run(mut tutor: Tutor) {
|
||||
loop {
|
||||
tutor.current_pos = 0;
|
||||
tutor.update(Some(
|
||||
"
|
||||
// Welcome to the mers tutor!
|
||||
// This is the main menu. Change the number to navigate to a specific part.
|
||||
fn go_to() 0
|
||||
// 1 Comments
|
||||
// 2 Functions
|
||||
// 3 Values
|
||||
// 4 Variables
|
||||
// 5 Returns
|
||||
// 6 Types
|
||||
// 7 Error handling
|
||||
|
||||
go_to()
|
||||
",
|
||||
));
|
||||
loop {
|
||||
match tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
VDataEnum::Int(pos) if pos != 0 => {
|
||||
tutor.current_pos = (pos.max(0) as usize).min(MAX_POS);
|
||||
match tutor.current_pos {
|
||||
0 => continue,
|
||||
1 => super::base_comments::run(&mut tutor),
|
||||
2 => super::base_functions::run(&mut tutor),
|
||||
3 => super::base_values::run(&mut tutor),
|
||||
4 => super::base_variables::run(&mut tutor),
|
||||
5 => super::base_return::run(&mut tutor),
|
||||
6 => super::base_types::run(&mut tutor),
|
||||
7 => super::error_handling::run(&mut tutor),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
other => {
|
||||
tutor.set_status(format!(
|
||||
" - Returned {} instead of a nonzero integer",
|
||||
other
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
use std::{path::PathBuf, thread::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
lang::{code_runnable::RScript, fmtgs::FormatGs, val_data::VDataEnum},
|
||||
parsing::{self, file::File},
|
||||
};
|
||||
|
||||
mod base_comments;
|
||||
mod base_functions;
|
||||
mod base_return;
|
||||
mod base_types;
|
||||
mod base_values;
|
||||
mod base_variables;
|
||||
mod error_handling;
|
||||
mod menu;
|
||||
|
||||
pub fn start(spawn_new_terminal_for_editor: bool) {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let (editor_join_handle, file_path) = crate::interactive_mode::fs_watcher::main(
|
||||
spawn_new_terminal_for_editor,
|
||||
"// Welcome to the mers tutor!
|
||||
|
||||
// This is an interactive experience. After making a change to this file,
|
||||
// save and then reload it to see the tutor's updates.
|
||||
// DO NOT save the file twice without reloading because you might overwrite changes made by the tutor,
|
||||
// which can completely ruin the file's formatting until the next full update (page change)!
|
||||
// To begin, change the following value from false to true:
|
||||
|
||||
false
|
||||
",
|
||||
Box::new(move |file| {
|
||||
let mut file =
|
||||
parsing::file::File::new(std::fs::read_to_string(file).unwrap(), PathBuf::new());
|
||||
sender.send((parsing::parse::parse(&mut file), file)).unwrap();
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let mut tutor = Tutor {
|
||||
current_pos: 0,
|
||||
current_status: String::new(),
|
||||
written_status_byte_len: 0,
|
||||
editor_join_handle,
|
||||
file_path,
|
||||
receiver,
|
||||
i_name: None,
|
||||
};
|
||||
loop {
|
||||
if let VDataEnum::Bool(true) = tutor.let_user_make_change().run(vec![]).inner_cloned() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
menu::run(tutor);
|
||||
}
|
||||
|
||||
use menu::MAX_POS;
|
||||
|
||||
pub struct Tutor {
|
||||
current_pos: usize,
|
||||
current_status: String,
|
||||
written_status_byte_len: usize,
|
||||
editor_join_handle: JoinHandle<()>,
|
||||
file_path: PathBuf,
|
||||
receiver: std::sync::mpsc::Receiver<(Result<RScript, parsing::parse::Error>, File)>,
|
||||
// i_ are inputs from the user
|
||||
pub i_name: Option<String>,
|
||||
}
|
||||
impl Tutor {
|
||||
/// only returns after a successful compile. before returning, does not call self.update() - you have to do that manually.
|
||||
pub fn let_user_make_change(&mut self) -> RScript {
|
||||
// eprintln!(" - - - - - - - - - - - - - - - - - - - - - - - - -");
|
||||
let script = loop {
|
||||
match self.receiver.recv().unwrap() {
|
||||
(Err(e), file) => {
|
||||
self.current_status = format!(
|
||||
" - Error during build{}",
|
||||
e.with_file(&file)
|
||||
.to_string()
|
||||
.lines()
|
||||
.map(|v| format!("\n// {v}"))
|
||||
.collect::<String>()
|
||||
)
|
||||
}
|
||||
(Ok(script), _) => {
|
||||
break script;
|
||||
}
|
||||
}
|
||||
self.update(None);
|
||||
};
|
||||
self.current_status = format!(" - OK");
|
||||
script
|
||||
}
|
||||
pub fn set_status(&mut self, new_status: String) {
|
||||
self.current_status = new_status;
|
||||
}
|
||||
pub fn update(&mut self, overwrite_contents_with: Option<&str>) {
|
||||
if self.editor_join_handle.is_finished() {
|
||||
eprintln!("Error has closed!");
|
||||
std::process::exit(0);
|
||||
}
|
||||
let string = std::fs::read_to_string(self.file_path()).unwrap();
|
||||
let status = format!(
|
||||
"// Tutor: {}/{MAX_POS}{}\n",
|
||||
self.current_pos, self.current_status,
|
||||
);
|
||||
let status_len = status.len();
|
||||
std::fs::write(
|
||||
self.file_path(),
|
||||
if let Some(new_content) = overwrite_contents_with {
|
||||
status + new_content
|
||||
} else {
|
||||
status + &string[self.written_status_byte_len..]
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
self.written_status_byte_len = status_len;
|
||||
// ignore this update to the file
|
||||
_ = self.receiver.recv().unwrap();
|
||||
}
|
||||
pub fn file_path(&self) -> &PathBuf {
|
||||
&self.file_path
|
||||
}
|
||||
}
|
||||
15
mers/test.mers
Executable file
15
mers/test.mers
Executable file
@@ -0,0 +1,15 @@
|
||||
list := (
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
);
|
||||
iter := (list item -> { item.println 12 }).map
|
||||
"---".println
|
||||
list := iter.as_list
|
||||
list.println
|
||||
@@ -1,3 +0,0 @@
|
||||
[a, [b, c]] := [1, ["str", 12.5]]
|
||||
|
||||
a == 1 && b == "str" && c == 12.5
|
||||
@@ -1,3 +0,0 @@
|
||||
fn plus(a int, b int) -> int { a + b }
|
||||
|
||||
10.plus(20) == 30
|
||||
@@ -1,18 +0,0 @@
|
||||
type person [string int]
|
||||
fn name(p person/&person) p.0
|
||||
fn age(p person/&person) p.1
|
||||
|
||||
type village [[float float] string]
|
||||
fn name(v village/&village) v.1
|
||||
fn location(v village/&village) v.0
|
||||
fn x_coordinate(v village/&village) v.0.0
|
||||
fn y_coordinate(v village/&village) v.0.1
|
||||
|
||||
|
||||
|
||||
customer := ["Max M.", 43]
|
||||
home_town := [[12.3, 5.09], "Maxburg"]
|
||||
|
||||
customer.name() == "Max M."
|
||||
&& home_town.name() == "Maxburg"
|
||||
&& home_town.location() == [12.3, 5.09]
|
||||
@@ -1,10 +0,0 @@
|
||||
list := [1 2 3 4 5 6 7 8 9 ...]
|
||||
|
||||
// calling get on an &list will get a reference
|
||||
&list.get(2).assume1() = 24
|
||||
// calling get on a list will get a value
|
||||
should_not_be_changeable := list.get(3).assume1()
|
||||
&should_not_be_changeable = 24
|
||||
|
||||
list.get(2) == [24]
|
||||
&& list.get(3) != [24]
|
||||
@@ -1,36 +0,0 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use mers_libs::{prelude::*, GlobalScriptInfo};
|
||||
use mers_libs::{ByteData, ByteDataA};
|
||||
|
||||
#[test]
|
||||
fn list_type() {
|
||||
let a: Vec<i32> = vec![14, 26];
|
||||
let bytes = a.as_byte_data_vec();
|
||||
println!("{bytes:?}");
|
||||
assert_eq!(
|
||||
Vec::<i32>::from_byte_data(&mut Cursor::new(bytes)).unwrap(),
|
||||
a
|
||||
);
|
||||
|
||||
let a = VSingleType::List(VSingleType::Int.to()).to();
|
||||
assert!(
|
||||
VType::from_byte_data(&mut Cursor::new(a.as_byte_data_vec()))
|
||||
.unwrap()
|
||||
.eq(&a, &GlobalScriptInfo::default()),
|
||||
);
|
||||
|
||||
let a = VSingleType::Tuple(vec![
|
||||
VType {
|
||||
types: vec![VSingleType::Tuple(vec![]), VSingleType::Int],
|
||||
},
|
||||
VSingleType::String.to(),
|
||||
VSingleType::EnumVariant(12, VSingleType::Float.to()).to(),
|
||||
])
|
||||
.to();
|
||||
assert!(
|
||||
VType::from_byte_data(&mut Cursor::new(a.as_byte_data_vec()))
|
||||
.unwrap()
|
||||
.eq(&a, &GlobalScriptInfo::default())
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
val := !(mers {
|
||||
"macro returned value"
|
||||
})
|
||||
|
||||
val := !(mers "my_macro.mers")
|
||||
|
||||
true
|
||||
@@ -1,9 +0,0 @@
|
||||
// NOTE: Might change, but this is the current state of things
|
||||
x := 10
|
||||
t := thread(() {
|
||||
sleep(0.25)
|
||||
x
|
||||
})
|
||||
&x = 20 // -> 20 20 because it modifies the original variable x
|
||||
// x := 20 // -> 10 20 because it shadows the original variable x
|
||||
t.await() == x
|
||||
@@ -1 +0,0 @@
|
||||
true
|
||||
@@ -1 +0,0 @@
|
||||
true
|
||||
@@ -1,27 +0,0 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use mers_libs::file::File;
|
||||
use mers_libs::{parse, VDataEnum};
|
||||
|
||||
#[test]
|
||||
fn run_all() {
|
||||
for file in fs::read_dir(Path::new(file!()).parent().unwrap())
|
||||
.unwrap()
|
||||
.filter_map(|v| v.ok())
|
||||
{
|
||||
if let Some(file_name) = file.file_name().to_str() {
|
||||
if file_name.ends_with(".mers") {
|
||||
eprintln!("Checking {}", file_name);
|
||||
let mut file = File::new(fs::read_to_string(file.path()).unwrap(), file.path());
|
||||
// has to return true, otherwise the test will fail
|
||||
assert!(
|
||||
matches!(
|
||||
parse::parse(&mut file).unwrap().run(vec![]).inner_cloned(),
|
||||
VDataEnum::Bool(true)
|
||||
),
|
||||
"{file_name} didn't return true!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// this is true by default so the example doesn't finish too quickly or too slowly depending on your hardware.
|
||||
// you can set it to false and tweak the max value for a more authentic cpu-heavy workload.
|
||||
fake_delay := true
|
||||
|
||||
// this will be shared between the two threads to report the progress in percent (0-100%).
|
||||
progress := 0
|
||||
|
||||
// an anonymous function that sums all numbers from 0 to max.
|
||||
// it captures the progress variable and uses it to report its status to the main thread, which will periodically print the current progress.
|
||||
// once done, it returns a string with the sum of all numbers.
|
||||
calculator := (max int) {
|
||||
sum := 0
|
||||
for i max {
|
||||
i := i + 1
|
||||
// println("i: {0} s: {1}".format(i.to_string() sum.to_string()))
|
||||
&sum = sum + i
|
||||
// if fake_delay sleep(1)
|
||||
&progress = i * 100 / max
|
||||
}
|
||||
"the sum of all numbers from 0 to {0} is {1}!".format(max.to_string() sum.to_string())
|
||||
}
|
||||
|
||||
// start the thread. if fake_delay is true, calculate 1+2+3+4+5+6+7+8+9+10. if fake_delay is false, count up to some ridiculously large number.
|
||||
slow_calculation_thread := calculator.thread(if fake_delay 10 else 20000000)
|
||||
|
||||
// every second, print the progress. once it reaches 100%, stop
|
||||
loop {
|
||||
sleep(1)
|
||||
println("{0}%".format(progress.to_string()))
|
||||
progress == 100 // break from the loop
|
||||
}
|
||||
|
||||
// use await() to get the result from the thread. if the thread is still running, this will block until the thread finishes.
|
||||
result := slow_calculation_thread.await()
|
||||
|
||||
println("Thread finished, result: {0}".format(result))
|
||||
|
||||
true
|
||||
Reference in New Issue
Block a user