full rewrite, kinda works

This commit is contained in:
Mark
2023-07-28 00:33:15 +02:00
parent 16258c7a0a
commit b81dac682e
96 changed files with 3150 additions and 1982 deletions

1550
mers/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"] }

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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()))
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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(&current_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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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,
};
}

View File

@@ -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"),
})
}
}

View File

@@ -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}.")
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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,
}),
},
})
}
}

View File

@@ -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 {
""
}
}
}

View File

@@ -1,2 +0,0 @@
pub mod file;
pub mod parse;

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
View 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

View File

@@ -1,3 +0,0 @@
[a, [b, c]] := [1, ["str", 12.5]]
a == 1 && b == "str" && c == 12.5

View File

@@ -1,3 +0,0 @@
fn plus(a int, b int) -> int { a + b }
10.plus(20) == 30

View File

@@ -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]

View File

@@ -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]

View File

@@ -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())
);
}

View File

@@ -1,7 +0,0 @@
val := !(mers {
"macro returned value"
})
val := !(mers "my_macro.mers")
true

View File

@@ -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

View File

@@ -1 +0,0 @@
true

View File

@@ -1 +0,0 @@
true

View File

@@ -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!"
);
}
}
}
}

View File

@@ -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