mirror of
https://github.com/Dummi26/mers.git
synced 2025-12-24 15:36:33 +01:00
moved Cargo.toml and src/ to /mers/Cargo.toml and /mers/src/ because rust-analyzer was apparently very confused when I was trying to edit projects in mers_libs/*/.
This commit is contained in:
42
mers/Cargo.lock
generated
Normal file
42
mers/Cargo.lock
generated
Normal file
@@ -0,0 +1,42 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
13
mers/Cargo.toml
Executable file
13
mers/Cargo.toml
Executable file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mers"
|
||||
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"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
regex = "1.7.2"
|
||||
139
mers/src/libs/inlib.rs
Executable file
139
mers/src/libs/inlib.rs
Executable file
@@ -0,0 +1,139 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{BufRead, Write},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
libs::DirectReader,
|
||||
script::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::VType,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{data_from_bytes, data_to_bytes};
|
||||
|
||||
pub struct MyLib {
|
||||
name: String,
|
||||
version: (u8, u8),
|
||||
description: String,
|
||||
functions: Vec<(String, Vec<VType>, VType)>,
|
||||
enum_variants: HashMap<String, usize>,
|
||||
}
|
||||
impl MyLib {
|
||||
pub fn new(
|
||||
name: String,
|
||||
version: (u8, u8),
|
||||
description: String,
|
||||
functions: Vec<(String, Vec<VType>, VType)>,
|
||||
) -> (Self, MyLibTaskCompletion) {
|
||||
(
|
||||
Self {
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
functions,
|
||||
enum_variants: HashMap::new(),
|
||||
},
|
||||
MyLibTaskCompletion { _priv: () },
|
||||
)
|
||||
}
|
||||
pub fn get_enum(&self, e: &str) -> usize {
|
||||
*self.enum_variants.get(e).unwrap()
|
||||
}
|
||||
pub fn run<I, O>(
|
||||
&mut self,
|
||||
run: MyLibTaskCompletion,
|
||||
stdin: &mut I,
|
||||
stdout: &mut O,
|
||||
) -> MyLibTask
|
||||
where
|
||||
I: BufRead,
|
||||
O: Write,
|
||||
{
|
||||
drop(run);
|
||||
match match stdin.one_byte().unwrap().into() {
|
||||
'i' => {
|
||||
assert_eq!(stdin.one_byte().unwrap() as char, '\n');
|
||||
stdout.write(&[self.version.0, self.version.1]).unwrap();
|
||||
writeln!(stdout, "{}", self.name).unwrap();
|
||||
stdout
|
||||
.write(&[self.description.split('\n').count() as _])
|
||||
.unwrap();
|
||||
writeln!(stdout, "{}", self.description).unwrap();
|
||||
for func in self.functions.iter() {
|
||||
writeln!(
|
||||
stdout,
|
||||
"f{}({}) {}",
|
||||
func.0,
|
||||
func.1
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| if i == 0 {
|
||||
format!("{v}")
|
||||
} else {
|
||||
format!(" {v}")
|
||||
})
|
||||
.collect::<String>(),
|
||||
func.2
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
writeln!(stdout, "x").unwrap();
|
||||
None
|
||||
}
|
||||
'I' => {
|
||||
let mut line = String::new();
|
||||
stdin.read_line(&mut line).unwrap();
|
||||
if let Some((task, args)) = line.split_once(' ') {
|
||||
match task {
|
||||
"set_enum_id" => {
|
||||
let (enum_name, enum_id) = args.split_once(' ').unwrap();
|
||||
let name = enum_name.trim().to_string();
|
||||
let id = enum_id.trim().parse().unwrap();
|
||||
self.enum_variants.insert(name.clone(), id);
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
'f' => {
|
||||
let fnid = stdin.one_byte().unwrap() as usize;
|
||||
Some(MyLibTask::RunFunction(MyLibTaskRunFunction {
|
||||
function: fnid,
|
||||
args: self.functions[fnid]
|
||||
.1
|
||||
.iter()
|
||||
.map(|_| data_from_bytes(stdin).to())
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
} {
|
||||
Some(v) => v,
|
||||
None => MyLibTask::None(MyLibTaskCompletion { _priv: () }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MyLibTask {
|
||||
None(MyLibTaskCompletion),
|
||||
RunFunction(MyLibTaskRunFunction),
|
||||
}
|
||||
pub struct MyLibTaskRunFunction {
|
||||
pub function: usize,
|
||||
pub args: Vec<VData>,
|
||||
}
|
||||
impl MyLibTaskRunFunction {
|
||||
pub fn done<O>(self, o: &mut O, returns: VData) -> MyLibTaskCompletion
|
||||
where
|
||||
O: Write,
|
||||
{
|
||||
data_to_bytes(&returns, o);
|
||||
MyLibTaskCompletion { _priv: () }
|
||||
}
|
||||
}
|
||||
pub struct MyLibTaskCompletion {
|
||||
_priv: (),
|
||||
}
|
||||
307
mers/src/libs/mod.rs
Executable file
307
mers/src/libs/mod.rs
Executable file
@@ -0,0 +1,307 @@
|
||||
pub mod inlib;
|
||||
pub mod path;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
io::{self, BufRead, BufReader, Read, Write},
|
||||
path::PathBuf,
|
||||
process::{Child, ChildStdin, ChildStdout, Command, Stdio},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
parse::{file::File, parse},
|
||||
script::{
|
||||
val_data::{VData, VDataEnum},
|
||||
val_type::VType,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
Libraries are processes that communicate via stdout/stdin.
|
||||
data in stdout is only expected after it was requested via stdin. ignoring this will likely cause issues.
|
||||
requests in stdin can be identified via the first byte (ascii char) and end with a \n newline character.
|
||||
the identifying ascii chars:
|
||||
i init
|
||||
reply format:
|
||||
two bytes, the first for major and the second for minor version number.
|
||||
the utf8-encoded name of the library followed by a newline
|
||||
the number of lines in the description (0 for no description) as a byte. (more than 255 lines aren't supported)
|
||||
a (optionally markdown-formatted [TODO!]) description of the library; all lines (including the last one) must end with a newline
|
||||
the things you would like to register; one line each unless explicitly stated otherwise; the first byte (char) decides what type to register:
|
||||
f a function: followed by the function signature, i.e. "my_func(string int/float [string]) string/[float int]"
|
||||
x end: indicates that you are done registering things
|
||||
I init 2
|
||||
TODO! (currently nothing)
|
||||
reply should be a single line (only the newline char). If there is data before the newline char, it will be reported as an error and the script will not run.
|
||||
f call a function:
|
||||
followed by the function id byte (0 <= id < #funcs; function ids are assigned in ascending order as they were registered in the reply to "i"
|
||||
and the data for each argument, in order.
|
||||
reply: the data for the returned value
|
||||
x exit
|
||||
sending data: (all ints are encoded so that the most significant data is sent FIRST)
|
||||
the first byte (ascii char) identifies the type of data: (exceptions listed first: bools)
|
||||
b false
|
||||
B true
|
||||
1 int
|
||||
2 int as string
|
||||
5 float
|
||||
6 float as string
|
||||
" string (format: ", 64-bit unsigned int indicating string length, so many bytes utf-8)
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Lib {
|
||||
process: Child,
|
||||
stdin: Arc<Mutex<ChildStdin>>,
|
||||
stdout: Arc<Mutex<BufReader<ChildStdout>>>,
|
||||
pub registered_fns: Vec<(String, Vec<VType>, VType)>,
|
||||
}
|
||||
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);
|
||||
writeln!(stdin, "i").unwrap();
|
||||
let vernum = {
|
||||
let mut vernum = [0, 0];
|
||||
stdout.read_exact(&mut vernum).unwrap();
|
||||
(vernum[0], vernum[1])
|
||||
};
|
||||
let name = stdout.line().unwrap();
|
||||
let name = name.trim();
|
||||
eprintln!("- <<< ADDING LIB: {name} v{}.{} >>> -", vernum.0, vernum.1);
|
||||
let lines_in_desc = stdout.one_byte().unwrap();
|
||||
let mut lines_desc = Vec::with_capacity(lines_in_desc as _);
|
||||
for _ in 0..lines_in_desc {
|
||||
let line = stdout.line().unwrap();
|
||||
let line = line.trim_end_matches('\n');
|
||||
eprintln!("| {line}");
|
||||
lines_desc.push(line.to_string());
|
||||
}
|
||||
let mut registered_fns = vec![];
|
||||
loop {
|
||||
let line = stdout.line().unwrap();
|
||||
match line.chars().next() {
|
||||
Some('f') => {
|
||||
let (name, args) = line[1..]
|
||||
.split_once('(')
|
||||
.expect("function signature didn't include the ( character.");
|
||||
let mut fn_signature = File::new(args.to_string(), PathBuf::new());
|
||||
let mut fn_in = vec![];
|
||||
fn_signature.skip_whitespaces();
|
||||
if let Some(')') = fn_signature.peek() {
|
||||
fn_signature.next();
|
||||
} else {
|
||||
loop {
|
||||
let mut t = parse::parse_type_adv(&mut fn_signature, true).unwrap();
|
||||
t.0.enum_variants(enum_variants);
|
||||
fn_in.push(t.0);
|
||||
if t.1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut fn_out = parse::parse_type(&mut fn_signature).unwrap();
|
||||
fn_out.enum_variants(enum_variants);
|
||||
eprintln!("Registering function \"{name}\" with args \"{}\" and return type \"{fn_out}\"", &fn_in.iter().fold(String::new(), |mut s, v| { s.push_str(format!(" {}", v).as_str()); s }).trim_start_matches(' '));
|
||||
registered_fns.push((name.to_string(), fn_in, fn_out));
|
||||
}
|
||||
Some('x') => break,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
for (enum_name, enum_id) in enum_variants.iter() {
|
||||
writeln!(stdin, "Iset_enum_id {enum_name} {enum_id}").unwrap();
|
||||
}
|
||||
Ok(Self {
|
||||
process: handle,
|
||||
stdin: Arc::new(Mutex::new(stdin)),
|
||||
stdout: Arc::new(Mutex::new(stdout)),
|
||||
registered_fns,
|
||||
})
|
||||
} else {
|
||||
return Err(LaunchError::NoStdio);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_fn(&self, fnid: usize, args: &Vec<VData>) -> VData {
|
||||
let mut stdin = self.stdin.lock().unwrap();
|
||||
let mut stdout = self.stdout.lock().unwrap();
|
||||
debug_assert!(args.len() == self.registered_fns[fnid].1.len());
|
||||
write!(stdin, "f").unwrap();
|
||||
stdin.write(&[fnid as _]).unwrap();
|
||||
for (_i, arg) in args.iter().enumerate() {
|
||||
data_to_bytes(arg, &mut *stdin);
|
||||
}
|
||||
let o = data_from_bytes(&mut *stdout).to();
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LaunchError {
|
||||
NoStdio,
|
||||
CouldNotSpawnProcess(io::Error),
|
||||
}
|
||||
|
||||
pub trait DirectReader {
|
||||
fn line(&mut self) -> Result<String, io::Error>;
|
||||
fn one_byte(&mut self) -> Result<u8, io::Error>;
|
||||
}
|
||||
impl<T> DirectReader for T
|
||||
where
|
||||
T: BufRead,
|
||||
{
|
||||
fn line(&mut self) -> Result<String, io::Error> {
|
||||
let mut buf = String::new();
|
||||
self.read_line(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
fn one_byte(&mut self) -> Result<u8, io::Error> {
|
||||
let mut b = [0];
|
||||
self.read(&mut b)?;
|
||||
Ok(b[0])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_to_bytes<T>(data: &VData, stdin: &mut T)
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
match &data.data {
|
||||
VDataEnum::Bool(false) => write!(stdin, "b").unwrap(),
|
||||
VDataEnum::Bool(true) => write!(stdin, "B").unwrap(),
|
||||
VDataEnum::Int(v) => {
|
||||
let mut v = *v;
|
||||
let mut b = [0u8; 8];
|
||||
for i in (0..8).rev() {
|
||||
b[i] = (v & 0xFF) as _;
|
||||
v >>= 8;
|
||||
}
|
||||
write!(stdin, "1").unwrap();
|
||||
stdin.write(&b).unwrap();
|
||||
}
|
||||
VDataEnum::Float(f) => {
|
||||
writeln!(stdin, "6{f}").unwrap();
|
||||
}
|
||||
VDataEnum::String(s) => {
|
||||
write!(stdin, "\"").unwrap();
|
||||
stdin.write(&(s.len() as u64).to_be_bytes()).unwrap();
|
||||
stdin.write(s.as_bytes()).unwrap();
|
||||
}
|
||||
VDataEnum::Tuple(v) => {
|
||||
write!(stdin, "t").unwrap();
|
||||
for v in v {
|
||||
write!(stdin, "+").unwrap();
|
||||
data_to_bytes(v, stdin);
|
||||
}
|
||||
writeln!(stdin).unwrap();
|
||||
}
|
||||
VDataEnum::List(_, v) => {
|
||||
write!(stdin, "l").unwrap();
|
||||
for v in v {
|
||||
write!(stdin, "+").unwrap();
|
||||
data_to_bytes(v, stdin);
|
||||
}
|
||||
writeln!(stdin).unwrap();
|
||||
}
|
||||
VDataEnum::Function(..) | VDataEnum::Reference(..) | VDataEnum::Thread(..) => {
|
||||
panic!("cannot use functions, references or threads in LibFunctions.")
|
||||
}
|
||||
VDataEnum::EnumVariant(e, v) => {
|
||||
stdin
|
||||
.write(
|
||||
['E' as u8]
|
||||
.into_iter()
|
||||
.chain((*e as u64).to_be_bytes().into_iter())
|
||||
.collect::<Vec<u8>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.unwrap();
|
||||
data_to_bytes(v.as_ref(), stdin);
|
||||
}
|
||||
}
|
||||
stdin.flush().unwrap();
|
||||
}
|
||||
pub fn data_from_bytes<T>(stdout: &mut T) -> VDataEnum
|
||||
where
|
||||
T: BufRead,
|
||||
{
|
||||
let id_byte = stdout.one_byte().unwrap().into();
|
||||
match id_byte {
|
||||
'b' => VDataEnum::Bool(false),
|
||||
'B' => VDataEnum::Bool(true),
|
||||
'1' => {
|
||||
let mut num = 0;
|
||||
for _ in 0..8 {
|
||||
num <<= 8;
|
||||
num |= stdout.one_byte().unwrap() as isize;
|
||||
}
|
||||
VDataEnum::Int(num)
|
||||
}
|
||||
'2' => {
|
||||
let mut buf = String::new();
|
||||
stdout.read_line(&mut buf).unwrap();
|
||||
VDataEnum::Int(buf.parse().unwrap())
|
||||
}
|
||||
'5' => {
|
||||
let mut num = 0;
|
||||
for _ in 0..8 {
|
||||
num <<= 8;
|
||||
num |= stdout.one_byte().unwrap() as u64;
|
||||
}
|
||||
VDataEnum::Float(f64::from_bits(num))
|
||||
}
|
||||
'6' => {
|
||||
let mut buf = String::new();
|
||||
stdout.read_line(&mut buf).unwrap();
|
||||
VDataEnum::Float(buf.parse().unwrap())
|
||||
}
|
||||
't' | 'l' => {
|
||||
let mut v = vec![];
|
||||
loop {
|
||||
if stdout.one_byte().unwrap() == '\n' as _ {
|
||||
break if id_byte == 't' {
|
||||
VDataEnum::Tuple(v)
|
||||
} else {
|
||||
VDataEnum::List(VType { types: vec![] }, v)
|
||||
};
|
||||
}
|
||||
v.push(data_from_bytes(stdout).to())
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
let mut len_bytes = 0u64;
|
||||
for _ in 0..8 {
|
||||
len_bytes <<= 8;
|
||||
len_bytes |= stdout.one_byte().unwrap() as u64;
|
||||
}
|
||||
let mut buf = Vec::with_capacity(len_bytes as _);
|
||||
for _ in 0..len_bytes {
|
||||
buf.push(stdout.one_byte().unwrap());
|
||||
}
|
||||
VDataEnum::String(String::from_utf8_lossy(&buf).into_owned())
|
||||
}
|
||||
'E' => {
|
||||
let mut u = [0u8; 8];
|
||||
stdout.read_exact(&mut u).unwrap();
|
||||
let u = u64::from_be_bytes(u) as _;
|
||||
VDataEnum::EnumVariant(u, Box::new(data_from_bytes(stdout).to()))
|
||||
}
|
||||
other => todo!("data_from_bytes: found '{other}'."),
|
||||
}
|
||||
}
|
||||
26
mers/src/libs/path.rs
Executable file
26
mers/src/libs/path.rs
Executable file
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn path_from_string(path: &str, script_directory: &PathBuf) -> Option<PathBuf> {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_absolute() {
|
||||
return Some(path);
|
||||
}
|
||||
if let Some(p) = script_directory
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| script_directory.clone())
|
||||
.parent()
|
||||
{
|
||||
eprintln!("Parent: {:?}", p);
|
||||
let p = p.join(&path);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
20
mers/src/main.rs
Executable file
20
mers/src/main.rs
Executable file
@@ -0,0 +1,20 @@
|
||||
use std::time::Instant;
|
||||
|
||||
pub mod libs;
|
||||
pub mod parse;
|
||||
pub mod script;
|
||||
|
||||
fn main() {
|
||||
let path = std::env::args().nth(1).unwrap();
|
||||
let script = parse::parse::parse(&mut parse::file::File::new(
|
||||
std::fs::read_to_string(&path).unwrap(),
|
||||
path.into(),
|
||||
))
|
||||
.unwrap();
|
||||
println!(" - - - - -");
|
||||
let start = Instant::now();
|
||||
let out = script.run(std::env::args().skip(2).collect());
|
||||
let elapsed = start.elapsed();
|
||||
println!(" - - - - -");
|
||||
println!("Output ({}s)\n{out}", elapsed.as_secs_f64());
|
||||
}
|
||||
185
mers/src/parse/file.rs
Executable file
185
mers/src/parse/file.rs
Executable file
@@ -0,0 +1,185 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Index, Range, RangeFrom, RangeTo},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub struct File {
|
||||
path: PathBuf,
|
||||
data: String,
|
||||
chars: Vec<(usize, char)>,
|
||||
pos: FilePosition,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FilePosition {
|
||||
current_char_index: usize,
|
||||
current_line: usize,
|
||||
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, self.current_column
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn new(data: String, path: PathBuf) -> Self {
|
||||
let mut chs = data.chars();
|
||||
let mut data = String::with_capacity(data.len());
|
||||
loop {
|
||||
match chs.next() {
|
||||
Some('/') => match chs.next() {
|
||||
Some('/') => loop {
|
||||
match chs.next() {
|
||||
Some('\n') | 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,
|
||||
}
|
||||
}
|
||||
let chars = data.char_indices().collect();
|
||||
Self {
|
||||
path,
|
||||
data,
|
||||
chars,
|
||||
pos: FilePosition {
|
||||
current_char_index: 0,
|
||||
current_line: 0,
|
||||
current_column: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn skip_whitespaces(&mut self) {
|
||||
loop {
|
||||
match self.chars.get(self.pos.current_char_index) {
|
||||
Some(ch) if ch.1.is_whitespace() => _ = self.next(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
pub fn get_pos(&self) -> &FilePosition {
|
||||
&self.pos
|
||||
}
|
||||
pub fn set_pos(&mut self, pos: FilePosition) {
|
||||
self.pos = pos;
|
||||
}
|
||||
pub fn get_line(&self) -> usize {
|
||||
self.pos.current_line
|
||||
}
|
||||
pub fn get_column(&self) -> usize {
|
||||
self.pos.current_column
|
||||
}
|
||||
pub fn get_char_index(&self) -> usize {
|
||||
self.pos.current_char_index
|
||||
}
|
||||
pub fn get_char(&self, index: usize) -> Option<char> {
|
||||
match self.chars.get(index) {
|
||||
Some(v) => Some(v.1),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
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> {
|
||||
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 {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
2
mers/src/parse/mod.rs
Executable file
2
mers/src/parse/mod.rs
Executable file
@@ -0,0 +1,2 @@
|
||||
pub mod file;
|
||||
pub mod parse;
|
||||
577
mers/src/parse/parse.rs
Executable file
577
mers/src/parse/parse.rs
Executable file
@@ -0,0 +1,577 @@
|
||||
use std::{process::Command, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
libs,
|
||||
script::{
|
||||
block::{
|
||||
to_runnable::ToRunnableError,
|
||||
to_runnable::{self, GInfo},
|
||||
RScript, SBlock, SFunction, SStatement, SStatementEnum,
|
||||
},
|
||||
val_data::VDataEnum,
|
||||
val_type::{VSingleType, VType},
|
||||
},
|
||||
};
|
||||
|
||||
use super::file::File;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScriptError {
|
||||
ParseError(ParseError),
|
||||
ToRunnableError(ToRunnableError),
|
||||
}
|
||||
impl From<ParseError> for ScriptError {
|
||||
fn from(value: ParseError) -> Self {
|
||||
Self::ParseError(value)
|
||||
}
|
||||
}
|
||||
impl From<ToRunnableError> for ScriptError {
|
||||
fn from(value: ToRunnableError) -> Self {
|
||||
Self::ToRunnableError(value)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {}
|
||||
|
||||
pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
|
||||
let mut libs = vec![];
|
||||
let mut enum_variants = GInfo::default_enum_variants();
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
let pos = file.get_pos().clone();
|
||||
let line = file.next_line();
|
||||
if line.starts_with("lib ") {
|
||||
let path_to_executable = match libs::path::path_from_string(&line[4..], file.path()) {
|
||||
Some(v) => v,
|
||||
None => panic!("Couldn't find a path for the library with the path '{}'. Maybe set the MERS_LIB_DIR env variable?", &line[4..]),
|
||||
};
|
||||
let mut cmd = Command::new(&path_to_executable);
|
||||
if let Some(parent) = path_to_executable.parent() {
|
||||
cmd.current_dir(parent.clone());
|
||||
}
|
||||
match libs::Lib::launch(cmd, &mut enum_variants) {
|
||||
Ok(lib) => {
|
||||
libs.push(lib);
|
||||
eprintln!("Loaded library!");
|
||||
}
|
||||
Err(e) => panic!(
|
||||
"Unable to load library at {}: {e:?}",
|
||||
path_to_executable.to_string_lossy().as_ref(),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
file.set_pos(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let func = SFunction::new(
|
||||
vec![(
|
||||
"args".to_string(),
|
||||
VSingleType::List(VSingleType::String.into()).into(),
|
||||
)],
|
||||
parse_block_advanced(file, Some(false), true, true, false)?,
|
||||
);
|
||||
eprintln!();
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("Parsed: {func}");
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("Parsed: {func:#?}");
|
||||
let run = to_runnable::to_runnable(func, GInfo::new(Arc::new(libs), enum_variants))?;
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("Runnable: {run:#?}");
|
||||
Ok(run)
|
||||
}
|
||||
|
||||
fn parse_block(file: &mut File) -> Result<SBlock, ParseError> {
|
||||
parse_block_advanced(file, None, true, false, false)
|
||||
}
|
||||
fn parse_block_advanced(
|
||||
file: &mut File,
|
||||
assume_single_statement: Option<bool>,
|
||||
treat_closed_block_bracket_as_closing_delimeter: bool,
|
||||
treat_eof_as_closing_delimeter: bool,
|
||||
treat_closed_normal_bracket_as_closing_delimeter: bool,
|
||||
) -> Result<SBlock, ParseError> {
|
||||
let mut statements = vec![];
|
||||
file.skip_whitespaces();
|
||||
let single_statement = if let Some(v) = assume_single_statement {
|
||||
v
|
||||
} else {
|
||||
if let Some('{') = file.get_char(file.get_char_index()) {
|
||||
file.next();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
match file.get_char(file.get_char_index()) {
|
||||
Some(')') if treat_closed_normal_bracket_as_closing_delimeter => {
|
||||
if single_statement {
|
||||
todo!("Err: closing function-arguments-delimeter in single-statement block before any statement (???fn with single-statement???)")
|
||||
} else {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some('}') if treat_closed_block_bracket_as_closing_delimeter => {
|
||||
if single_statement {
|
||||
todo!("Err: closing block-delimeter in single-statement block before any statement")
|
||||
} else {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
None if treat_eof_as_closing_delimeter => {
|
||||
break;
|
||||
}
|
||||
None => todo!("eof in block before statement"),
|
||||
_ => (),
|
||||
}
|
||||
statements.push(parse_statement(file)?);
|
||||
match file.peek() {
|
||||
// Some('}') if treat_closed_block_bracket_as_closing_delimeter => break,
|
||||
Some(')') if treat_closed_normal_bracket_as_closing_delimeter => {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if single_statement && !statements.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(SBlock::new(statements))
|
||||
}
|
||||
|
||||
fn parse_statement(file: &mut File) -> Result<SStatement, ParseError> {
|
||||
parse_statement_adv(file, false)
|
||||
}
|
||||
fn parse_statement_adv(
|
||||
file: &mut File,
|
||||
is_part_of_chain_already: bool,
|
||||
) -> Result<SStatement, ParseError> {
|
||||
file.skip_whitespaces();
|
||||
let mut start = String::new();
|
||||
let out = match file.peek() {
|
||||
Some('{') => Some(SStatementEnum::Block(parse_block(file)?).into()),
|
||||
Some('[') => {
|
||||
file.next();
|
||||
let mut v = vec![];
|
||||
let mut list = false;
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if let Some(']') = file.peek() {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
if file[file.get_char_index()..].starts_with("...]") {
|
||||
list = true;
|
||||
file.next();
|
||||
file.next();
|
||||
file.next();
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
v.push(parse_statement(file)?);
|
||||
}
|
||||
Some(if list {
|
||||
SStatementEnum::List(v).into()
|
||||
} else {
|
||||
SStatementEnum::Tuple(v).into()
|
||||
})
|
||||
}
|
||||
Some('"') => {
|
||||
file.next();
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match file.next() {
|
||||
Some('\\') => {
|
||||
if let Some(ch) = file.next() {
|
||||
buf.push(match ch {
|
||||
'\\' => '\\',
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
'"' => '"',
|
||||
ch => {
|
||||
eprintln!("Warn: Weird char escape \"\\{ch}\", will be replaced with \"{ch}\".");
|
||||
ch
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Some('"') => break,
|
||||
Some(ch) => buf.push(ch),
|
||||
None => todo!("Err: EOF in string"),
|
||||
}
|
||||
}
|
||||
Some(SStatementEnum::Value(VDataEnum::String(buf).to()).into())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let mut out = if let Some(out) = out {
|
||||
out
|
||||
} else {
|
||||
loop {
|
||||
match match file.peek() {
|
||||
Some(ch) if matches!(ch, '}' | ']' | ')' | '.') => Some(ch),
|
||||
_ => file.next(),
|
||||
} {
|
||||
Some('=') => {
|
||||
break parse_statement(file)?.output_to(start.trim().to_string());
|
||||
}
|
||||
Some(':') => {
|
||||
return Ok(SStatement::new(SStatementEnum::EnumVariant(
|
||||
start,
|
||||
parse_statement(file)?,
|
||||
)));
|
||||
}
|
||||
Some(ch) if ch.is_whitespace() || matches!(ch, '}' | ']' | ')' | '.') => {
|
||||
file.skip_whitespaces();
|
||||
if let Some('=') = file.peek() {
|
||||
continue;
|
||||
} else {
|
||||
let start = start.trim();
|
||||
match start {
|
||||
"fn" => {
|
||||
file.skip_whitespaces();
|
||||
let mut fn_name = String::new();
|
||||
loop {
|
||||
match file.next() {
|
||||
Some('(') => break,
|
||||
Some(ch) => fn_name.push(ch),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let func = parse_function(file)?;
|
||||
break SStatementEnum::FunctionDefinition(
|
||||
Some(fn_name.trim().to_string()),
|
||||
func,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
"if" => {
|
||||
// TODO: Else
|
||||
let condition = parse_statement(file)?;
|
||||
let then = parse_statement(file)?;
|
||||
let mut then_else = None;
|
||||
file.skip_whitespaces();
|
||||
let i = file.get_char_index();
|
||||
if file[i..].starts_with("else ") {
|
||||
while let Some('e' | 'l' | 's') = file.next() {}
|
||||
then_else = Some(parse_statement(file)?);
|
||||
}
|
||||
break SStatementEnum::If(condition, then, then_else).into();
|
||||
}
|
||||
"for" => {
|
||||
break SStatementEnum::For(
|
||||
{
|
||||
file.skip_whitespaces();
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
if let Some(ch) = file.next() {
|
||||
if ch.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
buf.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf
|
||||
},
|
||||
parse_statement(file)?,
|
||||
parse_statement(file)?,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
"while" => {
|
||||
break SStatementEnum::While(parse_statement(file)?).into();
|
||||
}
|
||||
"switch" | "switch!" => {
|
||||
let force = start.ends_with("!");
|
||||
let mut switch_on_what = String::new();
|
||||
loop {
|
||||
match file.next() {
|
||||
None => break,
|
||||
Some(ch) if ch.is_whitespace() => break,
|
||||
Some(ch) => switch_on_what.push(ch),
|
||||
}
|
||||
}
|
||||
file.skip_whitespaces();
|
||||
if let Some('{') = file.next() {
|
||||
} else {
|
||||
eprintln!("switch statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal.");
|
||||
}
|
||||
let mut cases = vec![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if let Some('}') = file.peek() {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
cases.push((parse_type(file)?, parse_statement(file)?));
|
||||
}
|
||||
break SStatementEnum::Switch(switch_on_what, cases, force).into();
|
||||
}
|
||||
"match" => {
|
||||
let mut match_what = String::new();
|
||||
loop {
|
||||
match file.next() {
|
||||
None => break,
|
||||
Some(ch) if ch.is_whitespace() => break,
|
||||
Some(ch) => match_what.push(ch),
|
||||
}
|
||||
}
|
||||
file.skip_whitespaces();
|
||||
if let Some('{') = file.next() {
|
||||
} else {
|
||||
eprintln!("match statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal.");
|
||||
}
|
||||
let mut cases = vec![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if let Some('}') = file.peek() {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
cases.push((parse_statement(file)?, parse_statement(file)?));
|
||||
}
|
||||
break SStatementEnum::Match(match_what, cases).into();
|
||||
}
|
||||
"true" => {
|
||||
break SStatementEnum::Value(VDataEnum::Bool(true).to()).into()
|
||||
}
|
||||
"false" => {
|
||||
break SStatementEnum::Value(VDataEnum::Bool(false).to()).into()
|
||||
}
|
||||
_ => {
|
||||
// int, float, var
|
||||
break {
|
||||
if let Ok(v) = start.parse() {
|
||||
SStatementEnum::Value(VDataEnum::Int(v).to()).into()
|
||||
} else if let Ok(v) = start.replace(",", ".").parse() {
|
||||
SStatementEnum::Value(VDataEnum::Float(v).to()).into()
|
||||
} else {
|
||||
if start.starts_with('&') {
|
||||
SStatementEnum::Variable(start[1..].to_string(), true)
|
||||
.into()
|
||||
} else {
|
||||
SStatementEnum::Variable(start.to_string(), false)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some('(') => {
|
||||
// parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise)
|
||||
let name = start.trim();
|
||||
if name.is_empty() {
|
||||
break SStatementEnum::FunctionDefinition(None, parse_function(file)?)
|
||||
.into();
|
||||
} else {
|
||||
break SStatementEnum::FunctionCall(
|
||||
name.to_string(),
|
||||
parse_block_advanced(file, Some(false), false, false, true)?.statements,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
Some(ch) => start.push(ch),
|
||||
None => todo!("EOF in statement"),
|
||||
}
|
||||
}
|
||||
};
|
||||
file.skip_whitespaces();
|
||||
if !file[file.get_char_index()..].starts_with("..") {
|
||||
// dot chain syntax only works if there is only one dot
|
||||
if let Some('.') = file.get_char(file.get_char_index()) {
|
||||
// consume the dot (otherwise, a.b.c syntax will break in certain cases)
|
||||
file.next();
|
||||
}
|
||||
if !is_part_of_chain_already {
|
||||
while let Some('.') = file.get_char(file.get_char_index().saturating_sub(1)) {
|
||||
let wrapper = parse_statement_adv(file, true)?;
|
||||
out = match *wrapper.statement {
|
||||
SStatementEnum::FunctionCall(func, args) => {
|
||||
let args = [out].into_iter().chain(args.into_iter()).collect();
|
||||
SStatementEnum::FunctionCall(func, args).into()
|
||||
}
|
||||
SStatementEnum::Value(vd) => match vd.data {
|
||||
VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, i as _).into(),
|
||||
_ => todo!("fixed-indexing not available with this type."),
|
||||
},
|
||||
other => {
|
||||
todo!("Wrapping in this type isn't implemented (yet?). Type: {other:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) <statement>"
|
||||
fn parse_function(file: &mut File) -> Result<SFunction, ParseError> {
|
||||
// find the arguments to the function
|
||||
let mut args = Vec::new();
|
||||
file.skip_whitespaces();
|
||||
loop {
|
||||
let mut arg_name = String::new();
|
||||
match file.next() {
|
||||
Some(')') => break,
|
||||
Some(ch) => arg_name.push(ch),
|
||||
None => break,
|
||||
}
|
||||
loop {
|
||||
match file.next() {
|
||||
Some(ch) if ch.is_whitespace() => break,
|
||||
Some(ch) => arg_name.push(ch),
|
||||
None => todo!("Err: EOF in function"),
|
||||
}
|
||||
}
|
||||
let (t, brk) = parse_type_adv(file, true)?;
|
||||
args.push((arg_name, t));
|
||||
if brk {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(SFunction::new(args, parse_block(file)?))
|
||||
}
|
||||
|
||||
pub(crate) fn parse_type(file: &mut File) -> Result<VType, ParseError> {
|
||||
match parse_type_adv(file, false) {
|
||||
Ok((v, _)) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
pub(crate) fn parse_type_adv(
|
||||
file: &mut File,
|
||||
in_fn_args: bool,
|
||||
) -> Result<(VType, bool), ParseError> {
|
||||
let mut types = vec![];
|
||||
let mut closed_fn_args = false;
|
||||
loop {
|
||||
let (st, closed_bracket) = parse_single_type_adv(file, in_fn_args)?;
|
||||
types.push(st);
|
||||
if closed_bracket {
|
||||
closed_fn_args = true;
|
||||
break;
|
||||
}
|
||||
file.skip_whitespaces();
|
||||
match file.peek() {
|
||||
Some('/') => {
|
||||
file.next();
|
||||
}
|
||||
Some(')') => {
|
||||
closed_fn_args = true;
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
Some(_) => break,
|
||||
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
Ok((VType { types }, closed_fn_args))
|
||||
}
|
||||
fn parse_single_type(file: &mut File) -> Result<VSingleType, ParseError> {
|
||||
match parse_single_type_adv(file, false) {
|
||||
Ok((v, _)) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn parse_single_type_adv(
|
||||
file: &mut File,
|
||||
in_fn_args: bool,
|
||||
) -> Result<(VSingleType, bool), ParseError> {
|
||||
file.skip_whitespaces();
|
||||
let mut closed_bracket_in_fn_args = false;
|
||||
Ok((
|
||||
match file.next() {
|
||||
Some('&') => {
|
||||
let parse_output = parse_single_type_adv(file, in_fn_args)?;
|
||||
if parse_output.1 {
|
||||
closed_bracket_in_fn_args = true;
|
||||
}
|
||||
VSingleType::Reference(Box::new(parse_output.0))
|
||||
}
|
||||
// Tuple or Array
|
||||
Some('[') => {
|
||||
let mut types = vec![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
match file.peek() {
|
||||
Some(']') => {
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
types.push(parse_type(file)?);
|
||||
}
|
||||
if in_fn_args {
|
||||
file.skip_whitespaces();
|
||||
if let Some(')') = file.peek() {
|
||||
closed_bracket_in_fn_args = true;
|
||||
file.next();
|
||||
}
|
||||
}
|
||||
if types.len() == 1 {
|
||||
VSingleType::List(types.pop().unwrap())
|
||||
} else {
|
||||
VSingleType::Tuple(types)
|
||||
}
|
||||
}
|
||||
Some(ch) => 'parse_single_type: {
|
||||
let mut name = ch.to_string();
|
||||
loop {
|
||||
match file.peek() {
|
||||
Some(']') => break,
|
||||
Some('/') => break,
|
||||
_ => (),
|
||||
}
|
||||
match file.next() {
|
||||
Some(ch) if ch.is_whitespace() => break,
|
||||
Some('(') => {
|
||||
break 'parse_single_type if name.as_str() == "fn" {
|
||||
todo!("fn types");
|
||||
} else {
|
||||
VSingleType::EnumVariantS(name, {
|
||||
let po = parse_type_adv(file, true)?;
|
||||
if !po.1 {
|
||||
// eprintln!("enum type should be closed by ')', but apparently wasn't?");
|
||||
assert_eq!(file.next(), Some(')'));
|
||||
}
|
||||
po.0
|
||||
})
|
||||
};
|
||||
}
|
||||
Some(')') if in_fn_args => {
|
||||
closed_bracket_in_fn_args = true;
|
||||
break;
|
||||
}
|
||||
Some(ch) => name.push(ch),
|
||||
None => todo!("Err: EOF in type"),
|
||||
}
|
||||
}
|
||||
match name.trim().to_lowercase().as_str() {
|
||||
"bool" => VSingleType::Bool,
|
||||
"int" => VSingleType::Int,
|
||||
"float" => VSingleType::Float,
|
||||
"string" => VSingleType::String,
|
||||
_ => {
|
||||
eprintln!("in_fn_args: {in_fn_args}");
|
||||
todo!("Err: Invalid type: \"{}\"", name.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
None => todo!("Err: EOF in type (1)"),
|
||||
},
|
||||
closed_bracket_in_fn_args,
|
||||
))
|
||||
}
|
||||
1153
mers/src/script/block.rs
Executable file
1153
mers/src/script/block.rs
Executable file
File diff suppressed because it is too large
Load Diff
1413
mers/src/script/builtins.rs
Executable file
1413
mers/src/script/builtins.rs
Executable file
File diff suppressed because it is too large
Load Diff
5
mers/src/script/mod.rs
Executable file
5
mers/src/script/mod.rs
Executable file
@@ -0,0 +1,5 @@
|
||||
pub mod block;
|
||||
pub mod builtins;
|
||||
pub mod val_data;
|
||||
pub mod val_type;
|
||||
pub mod value;
|
||||
220
mers/src/script/val_data.rs
Executable file
220
mers/src/script/val_data.rs
Executable file
@@ -0,0 +1,220 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, Mutex},
|
||||
thread::JoinHandle,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{
|
||||
block::RFunction,
|
||||
val_type::{VSingleType, VType},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VData {
|
||||
// parents: Vec<()>,
|
||||
pub data: VDataEnum,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VDataEnum {
|
||||
Bool(bool),
|
||||
Int(isize),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Tuple(Vec<VData>),
|
||||
List(VType, Vec<VData>),
|
||||
Function(RFunction),
|
||||
Thread(VDataThread, VType),
|
||||
Reference(Arc<Mutex<VData>>),
|
||||
EnumVariant(usize, Box<VData>),
|
||||
}
|
||||
impl PartialEq for VDataEnum {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Reference(a), Self::Reference(b)) => *a.lock().unwrap() == *b.lock().unwrap(),
|
||||
(Self::Reference(a), b) => a.lock().unwrap().data == *b,
|
||||
(a, Self::Reference(b)) => *a == b.lock().unwrap().data,
|
||||
(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 VData {
|
||||
pub fn out(&self) -> VType {
|
||||
VType {
|
||||
types: vec![self.out_single()],
|
||||
}
|
||||
}
|
||||
pub fn out_single(&self) -> VSingleType {
|
||||
match &self.data {
|
||||
VDataEnum::Bool(..) => VSingleType::Bool,
|
||||
VDataEnum::Int(..) => VSingleType::Int,
|
||||
VDataEnum::Float(..) => VSingleType::Float,
|
||||
VDataEnum::String(..) => VSingleType::String,
|
||||
VDataEnum::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out()).collect()),
|
||||
VDataEnum::List(t, _) => VSingleType::List(t.clone()),
|
||||
VDataEnum::Function(f) => VSingleType::Function(f.input_output_map.clone()),
|
||||
VDataEnum::Thread(_, o) => VSingleType::Thread(o.clone()),
|
||||
VDataEnum::Reference(r) => r.lock().unwrap().out_single(),
|
||||
VDataEnum::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out()),
|
||||
}
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<Self> {
|
||||
self.data.get(i)
|
||||
}
|
||||
pub fn noenum(self) -> Self {
|
||||
self.data.noenum()
|
||||
}
|
||||
}
|
||||
|
||||
impl VDataEnum {
|
||||
pub fn to(self) -> VData {
|
||||
VData { data: self }
|
||||
}
|
||||
}
|
||||
|
||||
// get()
|
||||
impl VDataEnum {
|
||||
pub fn noenum(self) -> VData {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) => *v,
|
||||
v => v.to(),
|
||||
}
|
||||
}
|
||||
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.lock().unwrap().get(i),
|
||||
Self::EnumVariant(_, v) => v.get(i),
|
||||
}
|
||||
}
|
||||
pub fn matches_ref_bool(&self) -> bool {
|
||||
match self {
|
||||
VDataEnum::Tuple(v) => !v.is_empty(),
|
||||
VDataEnum::Bool(false) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
pub fn matches(self) -> Option<VData> {
|
||||
match self {
|
||||
VDataEnum::Tuple(mut tuple) => tuple.pop(),
|
||||
VDataEnum::Bool(v) => {
|
||||
if v {
|
||||
Some(VDataEnum::Bool(v).to())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VDataEnum::EnumVariant(..) => None,
|
||||
other => Some(other.to()),
|
||||
}
|
||||
}
|
||||
}
|
||||
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) -> (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 = matches_as | t;
|
||||
}
|
||||
(can_fail, matches_as)
|
||||
}
|
||||
}
|
||||
|
||||
#[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: {v})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum VDataThreadEnum {
|
||||
Running(JoinHandle<VData>),
|
||||
Finished(VData),
|
||||
}
|
||||
impl VDataThreadEnum {
|
||||
pub fn to(self) -> VDataThread {
|
||||
VDataThread(Arc::new(Mutex::new(self)))
|
||||
}
|
||||
}
|
||||
245
mers/src/script/val_type.rs
Executable file
245
mers/src/script/val_type.rs
Executable file
@@ -0,0 +1,245 @@
|
||||
use std::{collections::HashMap, fmt::Debug, ops::BitOr};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VType {
|
||||
pub types: Vec<VSingleType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum VSingleType {
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Tuple(Vec<VType>),
|
||||
List(VType),
|
||||
Function(Vec<(Vec<VSingleType>, VType)>),
|
||||
Thread(VType),
|
||||
Reference(Box<Self>),
|
||||
EnumVariant(usize, VType),
|
||||
EnumVariantS(String, VType),
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
// None => Cannot get, Some(t) => getting can return t or nothing
|
||||
pub fn get(&self, i: usize) -> Option<VType> {
|
||||
match self {
|
||||
Self::Bool | Self::Int | Self::Float | Self::Function(..) | Self::Thread(..) => None,
|
||||
Self::String => Some(VSingleType::String.into()),
|
||||
Self::Tuple(t) => t.get(i).cloned(),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => r.get(i),
|
||||
Self::EnumVariant(_, t) | Self::EnumVariantS(_, t) => t.get(i),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn get(&self, i: usize) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out = out | t.get(i)?; // if we can't use *get* on one type, we can't use it at all.
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
pub fn get_any(&self) -> Option<VType> {
|
||||
match self {
|
||||
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 { types: vec![] }, |a, b| a | b)),
|
||||
Self::List(t) => Some(t.clone()),
|
||||
Self::Reference(r) => r.get_any(),
|
||||
Self::EnumVariant(_, t) => t.get_any(),
|
||||
Self::EnumVariantS(..) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl VType {
|
||||
pub fn get_any(&self) -> Option<VType> {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
out = out | t.get_any()?; // 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) -> Vec<VSingleType> {
|
||||
let mut no = vec![];
|
||||
for t in &self.types {
|
||||
// if t doesnt fit in any of rhs's types
|
||||
if !rhs.types.iter().any(|r| t.fits_in(r)) {
|
||||
no.push(t.clone())
|
||||
}
|
||||
}
|
||||
no
|
||||
}
|
||||
pub fn inner_types(&self) -> VType {
|
||||
let mut out = VType { types: vec![] };
|
||||
for t in &self.types {
|
||||
for it in t.inner_types() {
|
||||
out = out | it.to();
|
||||
}
|
||||
}
|
||||
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) -> bool {
|
||||
self.types.contains(t)
|
||||
}
|
||||
pub fn noenum(self) -> Self {
|
||||
let mut o = Self { types: vec![] };
|
||||
for t in self.types {
|
||||
o = o | t.noenum();
|
||||
}
|
||||
o
|
||||
}
|
||||
}
|
||||
impl BitOr for VType {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
let mut types = self.types;
|
||||
for t in rhs.types {
|
||||
if !types.contains(&t) {
|
||||
types.push(t)
|
||||
}
|
||||
}
|
||||
Self { types }
|
||||
}
|
||||
}
|
||||
impl BitOr<&VType> for VType {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: &Self) -> Self::Output {
|
||||
let mut types = self.types;
|
||||
for t in &rhs.types {
|
||||
if !types.contains(t) {
|
||||
types.push(t.clone())
|
||||
}
|
||||
}
|
||||
Self { types }
|
||||
}
|
||||
}
|
||||
|
||||
impl VSingleType {
|
||||
pub fn to(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
pub fn inner_types(&self) -> Vec<VSingleType> {
|
||||
match self {
|
||||
Self::Tuple(v) => {
|
||||
let mut types = vec![];
|
||||
for it in v {
|
||||
// the tuple values
|
||||
for it in &it.types {
|
||||
// the possible types for each value
|
||||
if !types.contains(it) {
|
||||
types.push(it.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
types
|
||||
}
|
||||
Self::List(v) => v.types.clone(),
|
||||
// NOTE: to make ints work in for loops
|
||||
Self::Int => vec![Self::Int],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
pub fn noenum(self) -> VType {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) | Self::EnumVariantS(_, v) => v,
|
||||
v => v.to(),
|
||||
}
|
||||
}
|
||||
pub fn enum_variants(&mut self, enum_variants: &mut HashMap<String, usize>) {
|
||||
match self {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn fits_in(&self, rhs: &Self) -> bool {
|
||||
match (self, rhs) {
|
||||
(Self::Reference(r), Self::Reference(b)) => r.fits_in(b),
|
||||
(Self::Reference(_), _) | (_, Self::Reference(_)) => false,
|
||||
(Self::EnumVariant(v1, t1), Self::EnumVariant(v2, t2)) => {
|
||||
*v1 == *v2 && t1.fits_in(&t2).is_empty()
|
||||
}
|
||||
(Self::EnumVariant(..), _) | (_, Self::EnumVariant(..)) => false,
|
||||
(Self::EnumVariantS(..), _) | (_, Self::EnumVariantS(..)) => unreachable!(),
|
||||
(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).is_empty())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Self::Tuple(_), _) => false,
|
||||
(Self::List(a), Self::List(b)) => a.fits_in(b).is_empty(),
|
||||
(Self::List(_), _) => false,
|
||||
(Self::Function(a), Self::Function(b)) => 'func_out: {
|
||||
for a in a {
|
||||
'search: {
|
||||
for b in b {
|
||||
if a.1.fits_in(&b.1).is_empty()
|
||||
&& a.0.iter().zip(b.0.iter()).all(|(a, b)| *a == *b)
|
||||
{
|
||||
break 'search;
|
||||
}
|
||||
}
|
||||
break 'func_out false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
(Self::Function(..), _) => false,
|
||||
(Self::Thread(a), Self::Thread(b)) => a.fits_in(b).is_empty(),
|
||||
(Self::Thread(..), _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<VType> for VSingleType {
|
||||
fn into(self) -> VType {
|
||||
VType { types: vec![self] }
|
||||
}
|
||||
}
|
||||
1
mers/src/script/value.rs
Executable file
1
mers/src/script/value.rs
Executable file
@@ -0,0 +1 @@
|
||||
// moved to val_data and val_type
|
||||
Reference in New Issue
Block a user