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

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
/mers/target
/mers/Cargo.lock
/mers_lib/target
/mers_lib/Cargo.lock
/mers_old/target
/mers_old/Cargo.lock
/mers_libs/*/target
/mers_libs/*/Cargo.lock
/build_scripts/nu_plugin_mers

326
README.md
View File

@ -1,302 +1,54 @@
# mers ![build status](https://github.com/Dummi26/mers/actions/workflows/rust.yml/badge.svg)
# mers
Mers is a simple and reliable programming language.
Mers is getting a rewrite!
While mers can already be used right now,
many things about it will still change -
at least until 1.0.0 is released.
For serious projects, this probably
isn't the language you should be using.
This means that this README isn't complete,
many things will change,
and the docs/ are for a completely different language.
## Why mers?
## why mers?
### it doesn't crash
### Simplicity
If your program starts, **it won't crash**.
The only exceptions to this are
the builtin `exit()` function, which can exit with a nonzero exit code
and the builtin `assume` functions, similar to Rust's `unwrap()`.
Mers aims to be very simple, as in, having very few "special" things.
But this means that many things you may be familiar with simply don't exist in mers,
because they aren't actually needed.
This includes:
This is because **errors in mers are values**,
and the compiler forces you to handle them.
**Exceptions**, because errors in mers are values just like any other.
A function to read a UTF-8 text file from disk could have a return type like `String/IOError/UTF8DecodeError`,
which tells you exactly what errors can happen and also forces you to handle them (see later for mers' type-system).
### type system
**Loops**, because, as it turns out, a function which takes an iterable and a function can do this just fine.
Javascript has `forEach`, Rust `for_each`, C# `Each`, and Java has `forEach`.
At first, it seemed stupid to have a function that does the same thing a `for` or `foreach` loop can already do,
but if a function can do the job, why do we even need a special language construct to do this for us?
To keep it simple, mers doesn't have any loops except for `loop`.
`loop` simply repeats until the inner expression returns `(T)`, which causes loop to return `T`.
In mers, a type can consist of multiple types:
**Breaks** aren't necessary, since this can be achieved using iterator magic or by returning `(T)` in a `loop`.
It is also similar to `goto`, because it makes control flow less obvious, so it had to go.
```
// bool
half := true
// float/int
number := if half { 0.5 } else { 1 }
The same control flow obfuscation issue exists for **returns**, so these also aren't a thing.
A function simply returns the value created by its expression.
// some math functions that work with int and float
fn absolute_value(num int/float) {
if num >= 0 { num } else { 0 - num }
}
fn difference(a int/float, b int/float) {
absolute_value(a - b)
}
**Functions** do exist, but they have one key difference: They take exactly one argument. Always.
Why? Because we don't need anything else.
A function with no arguments now takes an empty tuple `()`,
a function with two arguments now takes a two-length tuple `(a, b)`,
a function with either zero, one, or three arguments now takes a `()/(a)/(a, b, c)`,
and a function with n args takes a list, or a list as part of a tuple, or an optional list via `()/<the list>`.
debug(difference(0.2, 2)) // float: 1.8
// although difference can work with floats, since we only give it ints, it also returns an int.
// this is known at compile time.
debug(difference(20, 5)) // int: 15
```
### Types
With this, you can write code like you would in dynamically typed languages:
Mers is built around a type-system where a value could be one of multiple types.
This is basically what dynamic typing allows you to do:
```
user_input := if true { "some text input" } else { 19 }
```
x := if condition { 12 } else { "something went wrong" }
But you get the compile-time checks you love from statically typed ones:
This would be valid code.
However, in mers, the compiler still tracks all the types in your program,
and it will catch every possible crash before the program even runs:
If we tried to use `x` as an Int, the compiler would complain since it might be a string.
```
if user_input >= min_age {
println("welcome!")
}
```
Because `user_input` could be a string, the code above won't compile.
### it's simple
Almost everything in mers is built around this type system.
This eliminates many concepts you may be familiar with:
#### instead of null or exceptions, just add `[]` or the error(s) to the return type
```
// returns `[]` instead of dividing by zero
fn div_checked(a int, b int) -> int/[] {
if b != 0 {
a / b
} else {
[]
}
}
// returns the file content as a string.
// if the file can't be read, returns IoError and an error message.
// if the file's contents weren't Utf8, return NotUtf8 and the file's bytes.
fn load_file_as_string(path string) -> string/IoError(string)/NotUtf8([int ...]) {}
```
#### structs are nothing more than tuples
```
// define our type named Person
type Person [int, string]
// function to get (but not set) the person's birth year
fn birth_year(self Person) self.0
// function to get or set the person's name
fn name(self Person/&Person) self.1
person := [1999, "Ryan"]
// Get the values
person.name() // Ryan
person.birth_year() // 1999
// Change the name
&person.name() = "James"
// or: name(&person) = "James"
person.name() // James
// This doesn't compile:
// &person.birth_year() = 1998
```
#### enums are already part of the type system
```
type DirectoryEntry File(string)/Dir(string)/Link(DirectoryEntry)
```
#### return and break
Most people agree that `goto` statements are usually a bad idea.
But `return` and `break` have similar issues, although less severe:
**they create weird control flow:**
Maybe you want to see how long a function takes to execute, so
you add a `println` to the end - just to see absolutely no output.
Now you might think the function is stuck in an infinite loop somewhere,
although it just returned before it ever got to your println.
This can be annoying. If we remove `return` statements,
it becomes way easier to tell when code will or won't be executed.
**return is exclusive to functions, break to loops**
With mers, whenever possible, concepts in the language should work on *statements*.
An example of this are type annotations:
```
fn half(num int) -> int { num / 2 }
```
The `-> int` indicates that this function returns an int.
Actually, it indicates that the statement following it returns an int.
This obviously means the function will also return an int,
but since this syntax isn't specifically made for functions,
we can use it anywhere we want:
```
num := -> int { 4 + 5 }
half(-> int { double * 2})
```
**So how do we return values then?**
Simple!
```
// a function is just another statement
fn return_int() 4 + 5
// a block returns whatever its last statement returns,
fn return_string() {
a := "start"
b := "end"
a + " " + b
}
// this returns string/[], because there is no else
if condition() {
"some value"
}
// this returns string/int
if condition() {
"some value"
} else {
12
}
```
Most returns should be relatively intuitive,
although some special ones (like loops)
use matching (see docs/intro)
### it has references
To explain why this is necessary,
let's look at examples from other languages.
Here is some JavaScript:
```js
function doSmth(list) {
list[0] = 0
}
function modify(num) {
num = 0
}
list = [1]
num = 1
doSmth(list)
modify(num)
console.log(list) // [ 0 ]
console.log(num) // 1
```
The same thing in Go:
```go
package main
import "fmt"
func doSmth(list []int) {
list[0] = 0
}
func modify(num int) {
num = 0
}
func main() {
list := []int{1}
num := 1
doSmth(list)
modify(num)
fmt.Println(list) // [0]
fmt.Println(num) // 1
}
```
In both cases, the function was able to modify the list's contents,
but unable to change the number to a different value.
This is not just inconsistent, it's also not always what you wanted to do:
- i passed the list by value, how could the function change the inner value?
- the function is called `modify`, why can't it modify the value?
+ In Go, we could use references to make this work - but that just makes the list example even more annoying, since we don't need a reference to change the inner value.
So, let's look at mers:
```
fn doSmth(list [int ...]) {
&list.get(0).assume1() = 0
}
fn modify(num &int) {
num = 0
}
list := [1 ...]
num := 1
doSmth(list)
modify(&num)
debug(list) // [1 ...]
debug(num) // 0
```
The list is unchanged, but the number was changed by `modify`,
because it was explicitly passed as a reference `&int`.
**Unless you pass a reference, the value you passed will not be changed.**
### compile-time execution via macros
```
// at compile-time, runs primes.mers to find the first few prime numbers
primes := !(mers "primes.mers")
// at compile-time, runs the code to create a string
// containing 256 '-'s
long_string := !(mers {
str := "-"
loop {
str = str + str
if str.len() >= 256 {
str
}
}
})
```
## How mers?
There are prebuilt binaries in `build_scripts/`.
Mers is written in Rust. If you have `cargo`, you can also use the build script in `build_scripts/` to build it yourself.
Now, create a new text file (or choose one from the examples) and run it: `mers <file>`.
## Known Issues (only major ones)
### Multithreading
If a function is called from two threads, all local variables of that function are shared.
This doesn't affect builtin functions, and since functions usually don't take long to execute,
the chance of anyone encountering this is low, but it's something to be aware of.
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
## Docs
[intro](docs/intro.md)
[syntax cheat sheet](docs/syntax_cheat_sheet.md)
[builtins](docs/builtins.md)
[statements](docs/statements.md)
(note: type-checks aren't implemented since the rewrite is just barely functional, but they will be there and fully working soon)

302
README_old.md Executable file
View File

@ -0,0 +1,302 @@
# mers ![build status](https://github.com/Dummi26/mers/actions/workflows/rust.yml/badge.svg)
Mers is a simple and reliable programming language.
While mers can already be used right now,
many things about it will still change -
at least until 1.0.0 is released.
For serious projects, this probably
isn't the language you should be using.
## Why mers?
### it doesn't crash
If your program starts, **it won't crash**.
The only exceptions to this are
the builtin `exit()` function, which can exit with a nonzero exit code
and the builtin `assume` functions, similar to Rust's `unwrap()`.
This is because **errors in mers are values**,
and the compiler forces you to handle them.
### type system
In mers, a type can consist of multiple types:
```
// bool
half := true
// float/int
number := if half { 0.5 } else { 1 }
// some math functions that work with int and float
fn absolute_value(num int/float) {
if num >= 0 { num } else { 0 - num }
}
fn difference(a int/float, b int/float) {
absolute_value(a - b)
}
debug(difference(0.2, 2)) // float: 1.8
// although difference can work with floats, since we only give it ints, it also returns an int.
// this is known at compile time.
debug(difference(20, 5)) // int: 15
```
With this, you can write code like you would in dynamically typed languages:
```
user_input := if true { "some text input" } else { 19 }
```
But you get the compile-time checks you love from statically typed ones:
```
if user_input >= min_age {
println("welcome!")
}
```
Because `user_input` could be a string, the code above won't compile.
### it's simple
Almost everything in mers is built around this type system.
This eliminates many concepts you may be familiar with:
#### instead of null or exceptions, just add `[]` or the error(s) to the return type
```
// returns `[]` instead of dividing by zero
fn div_checked(a int, b int) -> int/[] {
if b != 0 {
a / b
} else {
[]
}
}
// returns the file content as a string.
// if the file can't be read, returns IoError and an error message.
// if the file's contents weren't Utf8, return NotUtf8 and the file's bytes.
fn load_file_as_string(path string) -> string/IoError(string)/NotUtf8([int ...]) {}
```
#### structs are nothing more than tuples
```
// define our type named Person
type Person [int, string]
// function to get (but not set) the person's birth year
fn birth_year(self Person) self.0
// function to get or set the person's name
fn name(self Person/&Person) self.1
person := [1999, "Ryan"]
// Get the values
person.name() // Ryan
person.birth_year() // 1999
// Change the name
&person.name() = "James"
// or: name(&person) = "James"
person.name() // James
// This doesn't compile:
// &person.birth_year() = 1998
```
#### enums are already part of the type system
```
type DirectoryEntry File(string)/Dir(string)/Link(DirectoryEntry)
```
#### return and break
Most people agree that `goto` statements are usually a bad idea.
But `return` and `break` have similar issues, although less severe:
**they create weird control flow:**
Maybe you want to see how long a function takes to execute, so
you add a `println` to the end - just to see absolutely no output.
Now you might think the function is stuck in an infinite loop somewhere,
although it just returned before it ever got to your println.
This can be annoying. If we remove `return` statements,
it becomes way easier to tell when code will or won't be executed.
**return is exclusive to functions, break to loops**
With mers, whenever possible, concepts in the language should work on *statements*.
An example of this are type annotations:
```
fn half(num int) -> int { num / 2 }
```
The `-> int` indicates that this function returns an int.
Actually, it indicates that the statement following it returns an int.
This obviously means the function will also return an int,
but since this syntax isn't specifically made for functions,
we can use it anywhere we want:
```
num := -> int { 4 + 5 }
half(-> int { double * 2})
```
**So how do we return values then?**
Simple!
```
// a function is just another statement
fn return_int() 4 + 5
// a block returns whatever its last statement returns,
fn return_string() {
a := "start"
b := "end"
a + " " + b
}
// this returns string/[], because there is no else
if condition() {
"some value"
}
// this returns string/int
if condition() {
"some value"
} else {
12
}
```
Most returns should be relatively intuitive,
although some special ones (like loops)
use matching (see docs/intro)
### it has references
To explain why this is necessary,
let's look at examples from other languages.
Here is some JavaScript:
```js
function doSmth(list) {
list[0] = 0
}
function modify(num) {
num = 0
}
list = [1]
num = 1
doSmth(list)
modify(num)
console.log(list) // [ 0 ]
console.log(num) // 1
```
The same thing in Go:
```go
package main
import "fmt"
func doSmth(list []int) {
list[0] = 0
}
func modify(num int) {
num = 0
}
func main() {
list := []int{1}
num := 1
doSmth(list)
modify(num)
fmt.Println(list) // [0]
fmt.Println(num) // 1
}
```
In both cases, the function was able to modify the list's contents,
but unable to change the number to a different value.
This is not just inconsistent, it's also not always what you wanted to do:
- i passed the list by value, how could the function change the inner value?
- the function is called `modify`, why can't it modify the value?
+ In Go, we could use references to make this work - but that just makes the list example even more annoying, since we don't need a reference to change the inner value.
So, let's look at mers:
```
fn doSmth(list [int ...]) {
&list.get(0).assume1() = 0
}
fn modify(num &int) {
num = 0
}
list := [1 ...]
num := 1
doSmth(list)
modify(&num)
debug(list) // [1 ...]
debug(num) // 0
```
The list is unchanged, but the number was changed by `modify`,
because it was explicitly passed as a reference `&int`.
**Unless you pass a reference, the value you passed will not be changed.**
### compile-time execution via macros
```
// at compile-time, runs primes.mers to find the first few prime numbers
primes := !(mers "primes.mers")
// at compile-time, runs the code to create a string
// containing 256 '-'s
long_string := !(mers {
str := "-"
loop {
str = str + str
if str.len() >= 256 {
str
}
}
})
```
## How mers?
There are prebuilt binaries in `build_scripts/`.
Mers is written in Rust. If you have `cargo`, you can also use the build script in `build_scripts/` to build it yourself.
Now, create a new text file (or choose one from the examples) and run it: `mers <file>`.
## Known Issues (only major ones)
### Multithreading
If a function is called from two threads, all local variables of that function are shared.
This doesn't affect builtin functions, and since functions usually don't take long to execute,
the chance of anyone encountering this is low, but it's something to be aware of.
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
## Docs
[intro](docs/intro.md)
[syntax cheat sheet](docs/syntax_cheat_sheet.md)
[builtins](docs/builtins.md)
[statements](docs/statements.md)

Binary file not shown.

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,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;
#[derive(Parser)]
struct Args {
#[arg(long, value_enum, default_value_t = Configs::Std)]
config: Configs,
#[command(subcommand)]
command: Command,
}
#[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() {
#[cfg(not(feature = "nushell_plugin"))]
normal_main();
#[cfg(feature = "nushell_plugin")]
nushell_plugin::main();
}
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),
let args = Args::parse();
let config = match args.config {
Configs::None => Config::new(),
Configs::Std => Config::new().bundle_std(),
};
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);
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);
}
}
}

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

4
mers_lib/Cargo.toml Executable file
View File

@ -0,0 +1,4 @@
[package]
name = "mers_lib"
version = "0.3.0"
edition = "2021"

53
mers_lib/doc.md Executable file
View File

@ -0,0 +1,53 @@
# mers documentation
## parsing
syntax:
- `// <comment>`
- `/* <comment> */`
operators:
- `<target> := <source>` init
- `<target> = <source>` assign (`<target>` must be a reference)
- `+`
- `-`
- `*`
- `/`
- `%`
- `&`
- `|`
- `&&`
- `||`
keywords (must be between whitespace):
- `if <condition> <statement>`
- `else <statement>` (after `if`)
- `loop <statement>`
- `switch { <arms> }`
- `<arg> -> <statement>`
- `def <name> <: for types, = for comptime> <_>` for compile-time stuff (types, macros, ...)
## details
### functions
A function takes an argument and returns some data:
func := input -> input + 2
3.func.println // 5
(list, 0).get // first element
(val, match -> match.println, [] -> "doesn't match".println).match
### switch
switch <val> {
<type> <func>
}
switch something {
int num -> {"int: " + num}.println
float num -> {"float: " + num}.println
}

15
mers_lib/init_to.rs Executable file
View File

@ -0,0 +1,15 @@
use crate::data::Data;
use super::MersStatement;
pub struct InitTo {
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}
impl MersStatement for InitTo {
fn has_scope(&self) -> bool {
false
}
fn run(&self, info: &mut super::Info) -> Data {}
}

47
mers_lib/src/data/bool.rs Executable file
View File

@ -0,0 +1,47 @@
use std::{any::Any, fmt::Display};
use super::{MersData, MersType};
#[derive(Debug, Clone)]
pub struct Bool(pub bool);
impl MersData for Bool {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct BoolT;
impl MersType for BoolT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for Bool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

13
mers_lib/src/data/defs.rs Executable file
View File

@ -0,0 +1,13 @@
use super::Data;
pub fn assign(from: Data, target: &Data) {
let mut target = target.get_mut();
if let Some(r) = target
.mut_any()
.downcast_mut::<crate::data::reference::Reference>()
{
*r.0.get_mut() = from.get().clone();
} else {
todo!("assignment to non-reference")
}
}

47
mers_lib/src/data/float.rs Executable file
View File

@ -0,0 +1,47 @@
use std::{any::Any, fmt::Display};
use super::{MersData, MersType, Type};
#[derive(Debug, Clone)]
pub struct Float(pub f64);
impl MersData for Float {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct FloatT;
impl MersType for FloatT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for Float {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

79
mers_lib/src/data/function.rs Executable file
View File

@ -0,0 +1,79 @@
use std::{
any::Any,
fmt::{Debug, Display},
sync::Arc,
};
use crate::program::{self, run::Info};
use super::{Data, MersData, MersType, Type};
#[derive(Clone)]
pub struct Function {
pub info: Info,
pub out: Arc<dyn Fn(&Type) -> Option<Type>>,
pub run: Arc<dyn Fn(Data, &mut crate::program::run::Info) -> Data>,
}
impl Function {
pub fn with_info(&self, info: program::run::Info) -> Self {
Self {
info,
out: Arc::clone(&self.out),
run: Arc::clone(&self.run),
}
}
pub fn run(&self, arg: Data) -> Data {
(self.run)(arg, &mut self.info.clone())
}
}
impl MersData for Function {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
pub struct FunctionT(Arc<dyn Fn(&Type) -> Option<Type>>);
impl MersType for FunctionT {
fn is_same_type_as(&self, _other: &dyn MersType) -> bool {
false
}
fn is_included_in_single(&self, _target: &dyn MersType) -> bool {
false
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Debug for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Function")
}
}
impl Debug for FunctionT {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FunctionT")
}
}
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<function>")
}
}

47
mers_lib/src/data/int.rs Executable file
View File

@ -0,0 +1,47 @@
use std::{any::Any, fmt::Display};
use super::{MersData, MersType, Type};
#[derive(Debug, Clone)]
pub struct Int(pub isize);
impl MersData for Int {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct IntT;
impl MersType for IntT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for Int {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

155
mers_lib/src/data/mod.rs Executable file
View File

@ -0,0 +1,155 @@
use std::{
any::Any,
fmt::{Debug, Display},
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
pub mod bool;
pub mod float;
pub mod function;
pub mod int;
pub mod reference;
pub mod string;
pub mod tuple;
pub mod defs;
pub trait MersData: Any + Debug + Display {
fn matches(&self) -> Option<Data> {
None
}
fn iterable(&self) -> Option<Box<dyn Iterator<Item = Data>>> {
None
}
/// By default, uses `iterable` to get an iterator and `nth` to retrieve the nth element.
/// Should have a custom implementation for better performance on most types
fn get(&self, i: usize) -> Option<Data> {
self.iterable()?.nth(i)
}
fn clone(&self) -> Box<dyn MersData>;
fn as_any(&self) -> &dyn Any;
fn mut_any(&mut self) -> &mut dyn Any;
fn to_any(self) -> Box<dyn Any>;
}
pub trait MersType: Any + Debug {
/// If Some((_, false)) is returned, data of this type could match. If it matches, it matches with the type.
/// If Some((_, true)) is returned, data of this type will always match with the type.
fn matches(&self) -> Option<(Type, bool)> {
None
}
/// If Some(T), calling `iterable` on the MersData this MersType belongs to
/// Should return Some(I), where I is an Iterator which only returns items of type T.
fn iterable(&self) -> Option<Type> {
None
}
/// If Some(T), calling `get` on data of this type may return T, but it might also return None.
/// By default, this returns the same thing as `iterable`, since this is also the default implementation for `MersData::get`.
fn get(&self) -> Option<Type> {
self.iterable()
}
/// If self and other are different types (`other.as_any().downcast_ref::<Self>().is_none()`),
/// this *must* return false.
fn is_same_type_as(&self, other: &dyn MersType) -> bool;
/// This doesn't handle the case where target is Type (is_included_in handles it)
fn is_included_in_single(&self, target: &dyn MersType) -> bool;
fn is_included_in(&self, target: &dyn MersType) -> bool {
if let Some(target) = target.as_any().downcast_ref::<Type>() {
target
.types
.iter()
.any(|t| self.is_included_in_single(t.as_ref()))
} else {
self.is_included_in_single(target)
}
}
fn as_any(&self) -> &dyn Any;
fn mut_any(&mut self) -> &mut dyn Any;
fn to_any(self) -> Box<dyn Any>;
}
#[derive(Debug)]
pub struct Data {
pub data: Arc<RwLock<Box<dyn MersData>>>,
}
impl Data {
pub fn new<T: MersData>(data: T) -> Self {
Self::new_boxed(Box::new(data))
}
pub fn new_boxed(data: Box<dyn MersData>) -> Self {
Self {
data: Arc::new(RwLock::new(data)),
}
}
pub fn empty_tuple() -> Self {
Self::new(tuple::Tuple(vec![]))
}
pub fn one_tuple(v: Self) -> Self {
Self::new(tuple::Tuple(vec![v]))
}
pub fn get(&self) -> RwLockReadGuard<Box<dyn MersData>> {
self.data.read().unwrap()
}
pub fn get_mut(&self) -> RwLockWriteGuard<Box<dyn MersData>> {
self.data.write().unwrap()
}
}
impl Clone for Data {
fn clone(&self) -> Self {
// todo!("clone for data - requires CoW");
Self {
data: Arc::clone(&self.data),
}
}
}
#[derive(Clone, Debug)]
pub struct Type {
// TODO: Maybe make sure this is always sorted by (recursive?!?) TypeId,
// that way is_same_type_as can work more efficiently (cuz good code but also branch prediction)
types: Vec<Arc<dyn MersType>>,
}
impl Type {
pub fn new<T: MersType>(t: T) -> Self {
Self {
types: vec![Arc::new(t)],
}
}
pub fn newm(types: Vec<Arc<dyn MersType>>) -> Self {
Self { types }
}
pub fn empty_tuple() -> Self {
Self::new(tuple::TupleT(vec![]))
}
pub fn add<T: MersType>(&mut self, new: Box<T>) {
todo!()
}
}
// PROBLEM:
// [int, int]/[int, string]/[string, int]/[string, string]
// is the same type as [int/string, int/string],
// but [int, int]/[int, string]/[string int] isn't.
// somehow, we need to merge these into the simplest form (same outer type, inner types differ)
// before we can implement `Type`
// idea: pick all the ones with the same first type: [int, int]/[int, string] and [string, int]/[string, string]
// then repeat with the second type if possible (here not, but for longer tuples, probably?)
// merge the last existing type in all the collections until we reach the first type again or the last types aren't equal anymore (how to check????)
impl MersType for Type {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
todo!()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
todo!()
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}

51
mers_lib/src/data/reference.rs Executable file
View File

@ -0,0 +1,51 @@
use std::{any::Any, fmt::Display, sync::Mutex};
use super::{Data, MersData, MersType, Type};
#[derive(Debug, Clone)]
pub struct Reference(pub Data);
impl MersData for Reference {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct ReferenceT(pub Type);
impl MersType for ReferenceT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
if let Some(o) = other.as_any().downcast_ref::<Self>() {
self.0.is_same_type_as(&o.0)
} else {
false
}
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for Reference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "&{}", self.0.get())
}
}

47
mers_lib/src/data/string.rs Executable file
View File

@ -0,0 +1,47 @@
use std::{any::Any, fmt::Display};
use super::{MersData, MersType, Type};
#[derive(Debug, Clone)]
pub struct String(pub std::string::String);
impl MersData for String {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct StringT;
impl MersType for StringT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for String {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

92
mers_lib/src/data/tuple.rs Executable file
View File

@ -0,0 +1,92 @@
use std::{any::Any, fmt::Display};
use super::{Data, MersData, MersType, Type};
#[derive(Debug, Clone)]
pub struct Tuple(pub Vec<Data>);
impl Tuple {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn get(&self, i: usize) -> Option<&Data> {
self.0.get(i)
}
}
impl MersData for Tuple {
fn matches(&self) -> Option<Data> {
if let Some(d) = self.0.first() {
if self.0.len() == 1 {
Some(d.clone())
} else {
None
}
} else {
None
}
}
fn iterable(&self) -> Option<Box<dyn Iterator<Item = Data>>> {
Some(Box::new(self.0.clone().into_iter()))
}
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
#[derive(Debug)]
pub struct TupleT(pub Vec<Type>);
impl MersType for TupleT {
fn matches(&self) -> Option<(Type, bool)> {
if let Some(d) = self.0.first() {
if self.0.len() == 1 {
Some((d.clone(), true))
} else {
None
}
} else {
None
}
}
fn iterable(&self) -> Option<Type> {
Some(todo!("joine types"))
}
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn Any {
self
}
fn mut_any(&mut self) -> &mut dyn Any {
self
}
fn to_any(self) -> Box<dyn Any> {
Box::new(self)
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
for (i, c) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", c.get())?;
}
write!(f, ")")?;
Ok(())
}
}

58
mers_lib/src/info/mod.rs Executable file
View File

@ -0,0 +1,58 @@
use std::fmt::Debug;
#[derive(Clone, Debug)]
pub struct Info<L: Local> {
pub scopes: Vec<L>,
}
impl<L: Local> Info<L> {
/// Returns self, but completely empty (even without globals).
/// Only use this if you assume this Info will never be used.
pub fn neverused() -> Self {
Self { scopes: vec![] }
}
}
pub trait Local: Default + Debug {
type VariableIdentifier;
type VariableData;
// type TypesIdentifier;
// type TypesType;
fn init_var(&mut self, id: Self::VariableIdentifier, value: Self::VariableData);
fn get_var(&self, id: &Self::VariableIdentifier) -> Option<&Self::VariableData>;
fn get_var_mut(&mut self, id: &Self::VariableIdentifier) -> Option<&mut Self::VariableData>;
// fn add_type(&mut self, id: Self::TypesIdentifier, new_type: Self::TypesType);
// fn get_type(&self, id: Self::TypesIdentifier) -> Option<&Self::TypesType>;
}
impl<L: Local> Info<L> {
pub fn create_scope(&mut self) {
self.scopes.push(L::default())
}
/// WARNING: can remove the last scope, which can cause some other functions to panic. Use ONLY after a create_scope()
pub fn end_scope(&mut self) {
self.scopes.pop();
}
}
impl<L: Local> Local for Info<L> {
type VariableIdentifier = L::VariableIdentifier;
type VariableData = L::VariableData;
fn init_var(&mut self, id: Self::VariableIdentifier, value: Self::VariableData) {
self.scopes.last_mut().unwrap().init_var(id, value)
}
fn get_var(&self, id: &Self::VariableIdentifier) -> Option<&Self::VariableData> {
self.scopes.iter().find_map(|l| l.get_var(id))
}
fn get_var_mut(&mut self, id: &Self::VariableIdentifier) -> Option<&mut Self::VariableData> {
self.scopes.iter_mut().find_map(|l| l.get_var_mut(id))
}
}
impl<L: Local> Default for Info<L> {
fn default() -> Self {
Self {
scopes: vec![L::default()],
}
}
}

12
mers_lib/src/lib.rs Executable file
View File

@ -0,0 +1,12 @@
pub mod data;
pub mod info;
pub mod parsing;
pub mod program;
pub mod prelude_compile {
pub use crate::parsing::parse;
pub use crate::parsing::Source;
pub use crate::program::configs::Config;
pub use crate::program::parsed::MersStatement as ParsedMersStatement;
pub use crate::program::run::MersStatement as RunMersStatement;
}

0
mers_lib/src/parsing/errors.rs Executable file
View File

156
mers_lib/src/parsing/mod.rs Executable file
View File

@ -0,0 +1,156 @@
use std::sync::Arc;
use crate::{
info::Info,
program::{self, parsed::CompInfo},
};
pub mod errors;
pub mod statements;
pub mod types;
pub fn parse(src: &mut Source) -> Result<Box<dyn program::parsed::MersStatement>, ()> {
Ok(Box::new(statements::parse_block(src)?))
}
pub struct Source {
src: String,
i: usize,
sections: Vec<SectionMarker>,
}
impl Source {
pub fn new(src: String) -> Self {
Self {
src,
i: 0,
sections: vec![],
}
}
pub fn skip_whitespace(&mut self) {
if let Some(i) = self.src[self.i..].char_indices().find_map(|(i, ch)| {
if !ch.is_whitespace() {
Some(i)
} else {
None
}
}) {
self.i += i;
} else {
self.i = self.src.len();
}
}
fn end_sections(&mut self) {
let pos = self.get_pos();
for section in self.sections.iter_mut() {
section.checked_end(pos);
}
}
pub fn peek_char(&self) -> Option<char> {
self.src[self.i..].chars().next()
}
pub fn next_char(&mut self) -> Option<char> {
self.end_sections();
let ch = self.src[self.i..].chars().next()?;
self.i += ch.len_utf8();
Some(ch)
}
fn word_splitter(ch: char) -> bool {
ch.is_whitespace() || ".,;)}".contains(ch)
}
pub fn peek_word(&self) -> &str {
self.src[self.i..]
.split(Self::word_splitter)
.next()
.unwrap_or("")
}
pub fn next_word(&mut self) -> &str {
self.end_sections();
let word = self.src[self.i..]
.split(Self::word_splitter)
.next()
.unwrap_or("");
self.i += word.len();
word
}
pub fn peek_line(&self) -> &str {
self.src[self.i..].lines().next().unwrap_or("")
}
pub fn next_line(&mut self) -> &str {
self.end_sections();
let line = self.src[self.i..].lines().next().unwrap_or("");
self.i += line.len();
line
}
pub fn get_pos(&self) -> SourcePos {
SourcePos(self.i)
}
pub fn set_pos(&mut self, new: SourcePos) {
self.i = new.0
}
/// Returns a SectionMarker which, when dropped, indicates the end of this section.
/// Useful for debugging the parser.
pub fn section_begin(&mut self, section: String) -> Arc<String> {
#[cfg(debug_assertions)]
println!("Section begin: {}", &section);
let arc = Arc::new(section);
self.sections.push(SectionMarker {
section: Arc::clone(&arc),
start: self.get_pos(),
end: None,
});
arc
}
pub fn sections(&self) -> &Vec<SectionMarker> {
&self.sections
}
}
impl Drop for Source {
fn drop(&mut self) {
self.end_sections()
}
}
/// Returned from Source::begin(), this indicates that parsing of
/// a certain section has not yet finished.
/// Once this is dropped, a section is considered done.
pub struct SectionMarker {
section: Arc<String>,
start: SourcePos,
end: Option<SourcePos>,
}
impl SectionMarker {
pub fn section(&self) -> &str {
&self.section
}
pub fn start(&self) -> &SourcePos {
&self.start
}
pub fn end(&self) -> &Option<SourcePos> {
&self.end
}
/// If this is the only remaining SectionMarker for this section,
/// this method sets its `end` property.
fn checked_end(&mut self, end: SourcePos) {
if self.end.is_none() {
if Arc::strong_count(&self.section) == 1 {
self.end = Some(end);
#[cfg(debug_assertions)]
println!("Section end : {}", &self.section);
}
}
}
}
#[derive(Clone, Copy)]
pub struct SourcePos(usize);
impl SourcePos {
fn diff(&self, rhs: &Self) -> usize {
rhs.0 - self.0
}
}

View File

@ -0,0 +1,199 @@
use std::sync::Arc;
use super::Source;
use crate::{
data::{self, Data},
program::{self, parsed::MersStatement},
};
pub fn parse(src: &mut Source) -> Result<Option<Box<dyn program::parsed::MersStatement>>, ()> {
src.section_begin("statement".to_string());
let mut first = if let Some(s) = parse_no_chain(src)? {
s
} else {
return Ok(None);
};
src.skip_whitespace();
match src.peek_word() {
":=" => {
src.next_word();
first = Box::new(program::parsed::init_to::InitTo {
target: first,
source: parse(src)?.expect("todo"),
});
}
"=" => {
src.next_word();
first = Box::new(program::parsed::assign_to::AssignTo {
target: first,
source: parse(src)?.expect("todo"),
});
}
"->" => {
src.next_word();
first = Box::new(program::parsed::function::Function {
arg: first,
run: parse(src)?.expect("err: bad eof, fn needs some statement"),
});
}
_ => loop {
src.skip_whitespace();
if let Some('.') = src.peek_char() {
src.next_char();
let chained = parse_no_chain(src)?.expect("err: EOF instead of chain");
first = Box::new(program::parsed::chain::Chain { first, chained });
} else {
break;
}
},
}
if matches!(src.peek_char(), Some(',' | ';')) {
src.next_char();
}
Ok(Some(first))
}
/// Assumes the { has already been parsed
pub fn parse_block(src: &mut Source) -> Result<program::parsed::block::Block, ()> {
Ok(program::parsed::block::Block {
statements: parse_multiple(src, '}')?,
})
}
pub fn parse_tuple(src: &mut Source) -> Result<program::parsed::tuple::Tuple, ()> {
Ok(program::parsed::tuple::Tuple {
elems: parse_multiple(src, ')')?,
})
}
pub fn parse_multiple(src: &mut Source, end: char) -> Result<Vec<Box<dyn MersStatement>>, ()> {
src.section_begin("block".to_string());
let mut statements = vec![];
loop {
src.skip_whitespace();
if matches!(src.peek_char(), Some(e) if e == end) {
src.next_char();
break;
} else if let Some(s) = parse(src)? {
statements.push(s);
} else {
// EOF
break;
}
}
Ok(statements)
}
pub fn parse_no_chain(
src: &mut Source,
) -> Result<Option<Box<dyn program::parsed::MersStatement>>, ()> {
src.section_begin("statement no chain".to_string());
src.skip_whitespace();
match src.peek_char() {
Some('{') => {
src.next_char();
return Ok(Some(Box::new(parse_block(src)?)));
}
Some('(') => {
src.next_char();
return Ok(Some(Box::new(parse_tuple(src)?)));
}
Some('"') => {
src.section_begin("string literal".to_string());
src.next_char();
let mut s = String::new();
loop {
if let Some(ch) = src.next_char() {
if ch == '\\' {
s.push(match src.next_char() {
Some('\\') => '\\',
Some('r') => '\r',
Some('n') => '\n',
Some('t') => '\t',
Some(o) => todo!("err: unknown backslash escape '\\{o}'"),
None => todo!("err: eof in backslash escape"),
});
} else if ch == '"' {
break;
} else {
s.push(ch);
}
} else {
todo!("err: eof in string")
}
}
return Ok(Some(Box::new(program::parsed::value::Value(Data::new(
crate::data::string::String(s),
)))));
}
_ => {}
}
Ok(Some(match src.next_word() {
"if" => {
src.section_begin("if".to_string());
Box::new(program::parsed::r#if::If {
condition: parse(src)?.expect("err: EOF instead of condition"),
on_true: parse(src)?.expect("err: EOF instead of on_true"),
on_false: {
src.skip_whitespace();
if src.peek_word() == "else" {
src.section_begin("else".to_string());
src.next_word();
Some(parse(src)?.expect("err: EOF instead of on_false after else"))
} else {
None
}
},
})
}
"loop" => {
src.section_begin("loop".to_string());
Box::new(program::parsed::r#loop::Loop {
inner: parse(src)?.expect("err: EOF instead of inner statement after loop"),
})
}
"switch" => {
src.section_begin("loop".to_string());
todo!()
}
"true" => Box::new(program::parsed::value::Value(Data::new(
crate::data::bool::Bool(true),
))),
"false" => Box::new(program::parsed::value::Value(Data::new(
crate::data::bool::Bool(false),
))),
"" => return Ok(None),
o => {
let o = o.to_string();
src.section_begin("literals, variables, and other non-keyword things".to_string());
if let Ok(n) = o.parse() {
if src.peek_char() == Some('.') {
let here = src.get_pos();
src.next_char();
if let Ok(num) = format!("{o}.{}", src.next_word()).parse() {
Box::new(program::parsed::value::Value(Data::new(
crate::data::float::Float(num),
)))
} else {
src.set_pos(here);
Box::new(program::parsed::value::Value(Data::new(
crate::data::int::Int(n),
)))
}
} else {
Box::new(program::parsed::value::Value(Data::new(
crate::data::int::Int(n),
)))
}
} else {
if let Some('&') = o.chars().next() {
Box::new(program::parsed::variable::Variable {
is_ref: true,
var: o[1..].to_string(),
})
} else {
Box::new(program::parsed::variable::Variable {
is_ref: false,
var: o.to_string(),
})
}
}
}
}))
}

51
mers_lib/src/parsing/types.rs Executable file
View File

@ -0,0 +1,51 @@
use super::Source;
/// multiple types are represented as a `Vec<ParsedType>`.
pub enum ParsedType {
Tuple(Vec<Vec<Self>>),
Type(String),
}
fn parse_single_type(src: &mut Source) -> Result<ParsedType, ()> {
src.section_begin("parse single type".to_string());
src.skip_whitespace();
Ok(match src.peek_char() {
// Tuple
Some('(') => {
src.next_char();
src.section_begin("parse tuple's inner types".to_string());
let mut inner = vec![];
loop {
match src.peek_char() {
Some(')') => {
src.next_char();
break;
}
Some(',') => {
src.next_char();
}
_ => todo!("err: bad char in tuple inner type"),
}
inner.push(parse_type(src)?);
}
ParsedType::Tuple(inner)
}
Some(_) => ParsedType::Type(src.next_word().to_lowercase()),
None => todo!(),
})
}
fn parse_type(src: &mut Source) -> Result<Vec<ParsedType>, ()> {
src.section_begin("parse single type".to_string());
let mut types = vec![];
loop {
types.push(parse_single_type(src)?);
src.skip_whitespace();
if let Some('/') = src.peek_char() {
continue;
} else {
break;
}
}
Ok(types)
}

View File

@ -0,0 +1,198 @@
use std::sync::Arc;
use crate::{
data::{self, Data, Type},
info::Local,
program,
};
mod with_command_running;
mod with_iters;
mod with_list;
/// Usage: create an empty Config using Config::new(), use the methods to customize it, then get the Infos using Config::infos()
/// bundle_* for bundles (combines multiple groups or even bundles)
/// with_* for usage-oriented groups
/// add_* to add custom things
///
/// For doc-comments:
/// Description
/// `bundle_std()`
/// `type` - description
/// `var: type` - description
pub struct Config {
globals: usize,
info_parsed: super::parsed::Info,
info_run: super::run::Info,
}
impl Config {
pub fn new() -> Self {
Self {
globals: 0,
info_parsed: Default::default(),
info_run: Default::default(),
}
}
/// standard utilitis used in many programs
/// `bundle_base()`
/// `with_list()`
/// `with_command_running()`
pub fn bundle_std(self) -> Self {
self.with_command_running().with_list().bundle_base()
}
/// base utilities used in most programs
/// `with_prints()`
/// `with_math()`
/// `with_get()`
/// `with_iters()`
pub fn bundle_base(self) -> Self {
self.with_iters().with_get().with_math().with_prints()
}
/// `println: fn` prints to stdout and adds a newline to the end
/// `print: fn` prints to stdout
/// `eprintln: fn` prints to stderr and adds a newline to the end
/// `eprint: fn` prints to stderr
/// `debug: fn` debug-prints any value
pub fn with_prints(self) -> Self {
self.add_var(
"debug".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
eprintln!("{:#?}", a.get());
Data::empty_tuple()
}),
}),
)
.add_var(
"eprint".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
eprint!("{}", a.get());
Data::empty_tuple()
}),
}),
)
.add_var(
"eprintln".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
eprintln!("{}", a.get());
Data::empty_tuple()
}),
}),
)
.add_var(
"print".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
print!("{}", a.get());
Data::empty_tuple()
}),
}),
)
.add_var(
"println".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
println!("{}", a.get());
Data::empty_tuple()
}),
}),
)
}
/// `sum: fn` returns the sum of all the numbers in the tuple
pub fn with_math(self) -> Self {
self.add_var(
"sum".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
let mut sumi = 0;
let mut sumf = 0.0;
let mut usef = false;
for val in &tuple.0 {
if let Some(i) = val.get().as_any().downcast_ref::<data::int::Int>() {
sumi += i.0;
} else if let Some(i) =
val.get().as_any().downcast_ref::<data::float::Float>()
{
sumf += i.0;
usef = true;
}
}
if usef {
Data::new(data::float::Float(sumi as f64 + sumf))
} else {
Data::new(data::int::Int(sumi))
}
} else {
unreachable!("sum called on non-tuple")
}
}),
}),
)
}
/// `get: fn` is used to retrieve elements from collections
pub fn with_get(self) -> Self {
self.add_var(
"get".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(v), Some(i)) = (tuple.get(0), tuple.get(1)) {
if let Some(i) = i.get().as_any().downcast_ref::<data::int::Int>() {
if let Ok(i) = i.0.try_into() {
if let Some(v) = v.get().get(i) {
Data::one_tuple(v)
} else {
Data::empty_tuple()
}
} else {
Data::empty_tuple()
}
} else {
unreachable!("get called with non-int index")
}
} else {
unreachable!("get called on tuple with len < 2")
}
} else {
unreachable!("get called on non-tuple, arg must be (_, index)")
}
}),
}),
)
}
pub fn add_var(mut self, name: String, val: Data) -> Self {
self.info_parsed.scopes[0].init_var(name, (0, self.globals));
self.info_run.scopes[0].init_var(self.globals, val);
self.globals += 1;
self
}
pub fn add_type(mut self, name: String, t: Type) -> Self {
// TODO! needed for type syntax in the parser, everything else probably(?) works already
self
}
pub fn infos(self) -> (super::parsed::Info, super::run::Info) {
(self.info_parsed, self.info_run)
}
}

View File

@ -0,0 +1,104 @@
use std::{fmt::Display, process::Command, sync::Arc};
use crate::{
data::{self, Data, MersData, MersType},
program,
};
use super::Config;
impl Config {
/// adds utilities to run commands installed on the system and get their output.
/// `run_command: fn` runs a command with arguments.
/// Args: (cmd, args) where cmd is a string and args is an Iterable over strings
/// `RunCommandError` holds the error if the command can't be executed
pub fn with_command_running(self) -> Self {
self.add_var(
"run_command".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(cmd) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(cmd), Some(args)) = (cmd.get(0), cmd.get(1)) {
if let (Some(cmd), Some(args)) = (
cmd.get().as_any().downcast_ref::<data::string::String>(),
args.get().iterable(),
) {
match Command::new(&cmd.0)
.args(args.map(|v| v.get().to_string()))
.output()
{
Ok(output) => {
let status = if let Some(code) = output.status.code() {
Data::new(data::int::Int(code as _))
} else {
Data::empty_tuple()
};
let stdout =
String::from_utf8_lossy(&output.stdout).into_owned();
let stderr =
String::from_utf8_lossy(&output.stderr).into_owned();
Data::new(data::tuple::Tuple(vec![
status,
Data::new(data::string::String(stdout)),
Data::new(data::string::String(stderr)),
]))
}
Err(e) => Data::new(RunCommandError(e.to_string())),
}
} else {
unreachable!("run_command called with arguments other than (String, <Iterable>).")
}
} else {
unreachable!("run_command called with too few arguments")
}
} else {
unreachable!("run_command called with non-tuple argument")
}
}),
}),
)
}
}
#[derive(Clone, Debug)]
pub struct RunCommandError(String);
#[derive(Debug)]
pub struct RunCommandErrorT;
impl MersData for RunCommandError {
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn to_any(self) -> Box<dyn std::any::Any> {
Box::new(self)
}
}
impl MersType for RunCommandErrorT {
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other.as_any().downcast_ref::<Self>().is_some()
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn to_any(self) -> Box<dyn std::any::Any> {
Box::new(self)
}
}
impl Display for RunCommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RunCommandError: {}", self.0)
}
}

View File

@ -0,0 +1,175 @@
use std::{fmt::Display, sync::Arc};
use crate::{
data::{self, Data, MersData},
program,
};
use super::Config;
impl Config {
/// Adds functions to deal with iterables
/// `iter: fn` executes a function once for each element of the iterable
pub fn with_iters(self) -> Self {
self.add_var(
"iter".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(v), Some(f)) = (tuple.get(0), tuple.get(1)) {
if let (Some(iter), Some(f)) = (
v.get().iterable(),
f.get().as_any().downcast_ref::<data::function::Function>(),
) {
for v in iter {
f.run(v);
}
Data::empty_tuple()
} else {
unreachable!(
"iter called on tuple not containing iterable and function"
)
}
} else {
unreachable!("iter called on tuple with len < 2")
}
} else {
unreachable!("iter called on non-tuple")
}
}),
}),
)
.add_var(
"map".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(v), Some(f)) = (tuple.get(0), tuple.get(1)) {
if let Some(f) =
f.get().as_any().downcast_ref::<data::function::Function>()
{
Data::new(Iter(Iters::Map(Clone::clone(f)), v.clone()))
} else {
unreachable!("iter called on tuple not containing function")
}
} else {
unreachable!("iter called on tuple with len < 2")
}
} else {
unreachable!("iter called on non-tuple")
}
}),
}),
)
.add_var(
"filter".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(v), Some(f)) = (tuple.get(0), tuple.get(1)) {
if let Some(f) =
f.get().as_any().downcast_ref::<data::function::Function>()
{
Data::new(Iter(Iters::Filter(Clone::clone(f)), v.clone()))
} else {
unreachable!("iter called on tuple not containing function")
}
} else {
unreachable!("iter called on tuple with len < 2")
}
} else {
unreachable!("iter called on non-tuple")
}
}),
}),
)
.add_var(
"filter_map".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(tuple) = a.get().as_any().downcast_ref::<data::tuple::Tuple>() {
if let (Some(v), Some(f)) = (tuple.get(0), tuple.get(1)) {
if let Some(f) =
f.get().as_any().downcast_ref::<data::function::Function>()
{
Data::new(Iter(Iters::FilterMap(Clone::clone(f)), v.clone()))
} else {
unreachable!("iter called on tuple not containing function")
}
} else {
unreachable!("iter called on tuple with len < 2")
}
} else {
unreachable!("iter called on non-tuple")
}
}),
}),
)
}
}
#[derive(Clone, Debug)]
pub enum Iters {
Map(data::function::Function),
Filter(data::function::Function),
FilterMap(data::function::Function),
}
#[derive(Clone, Debug)]
pub struct Iter(Iters, Data);
#[derive(Clone, Debug)]
pub struct IterT(Iters);
impl MersData for Iter {
fn iterable(&self) -> Option<Box<dyn Iterator<Item = Data>>> {
Some(match &self.0 {
Iters::Map(f) => {
let f = Clone::clone(f);
Box::new(self.1.get().iterable()?.map(move |v| f.run(v)))
}
Iters::Filter(f) => {
let f = Clone::clone(f);
Box::new(self.1.get().iterable()?.filter(move |v| {
f.run(v.clone())
.get()
.as_any()
.downcast_ref::<data::bool::Bool>()
.is_some_and(|b| b.0)
}))
}
Iters::FilterMap(f) => {
let f = Clone::clone(f);
Box::new(
self.1
.get()
.iterable()?
.filter_map(move |v| f.run(v).get().matches()),
)
}
_ => todo!(),
})
}
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn to_any(self) -> Box<dyn std::any::Any> {
Box::new(self)
}
}
impl Display for Iter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<Iter>")
}
}

View File

@ -0,0 +1,90 @@
use std::{fmt::Display, sync::Arc};
use crate::{
data::{self, Data, MersData, MersType, Type},
program,
};
use super::Config;
impl Config {
/// Adds a simple list type
/// `List` can store a variable number of items
/// `as_list: fn` turns a tuple into a list
pub fn with_list(self) -> Self {
// TODO: Type with generics
self.add_type("List".to_string(), Type::new(ListT(Type::empty_tuple())))
.add_var(
"as_list".to_string(),
Data::new(data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_a| todo!()),
run: Arc::new(|a, _i| {
if let Some(i) = a.get().iterable() {
Data::new(List(i.collect()))
} else {
unreachable!("as_list called on non-iterable")
}
}),
}),
)
}
}
#[derive(Clone, Debug)]
pub struct List(Vec<Data>);
#[derive(Debug)]
pub struct ListT(Type);
impl MersData for List {
fn iterable(&self) -> Option<Box<dyn Iterator<Item = Data>>> {
Some(Box::new(self.0.clone().into_iter()))
}
fn clone(&self) -> Box<dyn MersData> {
Box::new(Clone::clone(self))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn to_any(self) -> Box<dyn std::any::Any> {
Box::new(self)
}
}
impl MersType for ListT {
fn iterable(&self) -> Option<Type> {
Some(self.0.clone())
}
fn is_same_type_as(&self, other: &dyn MersType) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.is_some_and(|v| self.0.is_same_type_as(&v.0))
}
fn is_included_in_single(&self, target: &dyn MersType) -> bool {
self.is_same_type_as(target)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn mut_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn to_any(self) -> Box<dyn std::any::Any> {
Box::new(self)
}
}
impl Display for List {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[")?;
for (i, c) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", c.get())?;
}
write!(f, "]")?;
Ok(())
}
}

3
mers_lib/src/program/mod.rs Executable file
View File

@ -0,0 +1,3 @@
pub mod configs;
pub mod parsed;
pub mod run;

View File

@ -0,0 +1,25 @@
use crate::{info::Local, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct AssignTo {
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}
impl MersStatement for AssignTo {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
mut comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::assign_to::AssignTo {
target: self.target.compile(info, comp)?,
source: self.source.compile(info, comp)?,
}))
}
}

View File

@ -0,0 +1,26 @@
use crate::{info, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Block {
pub statements: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Block {
fn has_scope(&self) -> bool {
true
}
fn compile_custom(
&self,
info: &mut info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::block::Block {
statements: self
.statements
.iter()
.map(|v| v.compile(info, comp))
.collect::<Result<Vec<_>, _>>()?,
}))
}
}

View File

@ -0,0 +1,24 @@
use crate::{info, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Chain {
pub first: Box<dyn MersStatement>,
pub chained: Box<dyn MersStatement>,
}
impl MersStatement for Chain {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::chain::Chain {
first: self.first.compile(info, comp)?,
chained: self.chained.compile(info, comp)?,
}))
}
}

View File

@ -0,0 +1,42 @@
use std::sync::Arc;
use crate::{
data::{self, Data},
info::Local,
program,
};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Function {
pub arg: Box<dyn MersStatement>,
pub run: Box<dyn MersStatement>,
}
impl MersStatement for Function {
fn has_scope(&self) -> bool {
// TODO: what???
false
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
mut comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
comp.is_init = true;
let arg = self.arg.compile(info, comp)?;
comp.is_init = false;
let run = self.run.compile(info, comp)?;
Ok(Box::new(program::run::function::Function {
func_no_info: data::function::Function {
info: program::run::Info::neverused(),
out: Arc::new(|_i| todo!()),
run: Arc::new(move |i, info| {
data::defs::assign(i, &arg.run(info));
run.run(info)
}),
},
}))
}
}

View File

@ -0,0 +1,31 @@
use crate::program;
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct If {
pub condition: Box<dyn MersStatement>,
pub on_true: Box<dyn MersStatement>,
pub on_false: Option<Box<dyn MersStatement>>,
}
impl MersStatement for If {
fn has_scope(&self) -> bool {
true
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::r#if::If {
condition: self.condition.compile(info, comp)?,
on_true: self.condition.compile(info, comp)?,
on_false: if let Some(v) = &self.on_false {
Some(v.compile(info, comp)?)
} else {
None
},
}))
}
}

View File

@ -0,0 +1,29 @@
use crate::{info::Local, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct InitTo {
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}
impl MersStatement for InitTo {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
mut comp: CompInfo,
) -> Result<Box<dyn crate::program::run::MersStatement>, String> {
comp.is_init = true;
let target = self.target.compile(info, comp)?;
comp.is_init = false;
let source = self.source.compile(info, comp)?;
Ok(Box::new(program::run::assign_to::AssignTo {
target,
source,
}))
}
}

View File

@ -0,0 +1,23 @@
use crate::program;
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Loop {
pub inner: Box<dyn MersStatement>,
}
impl MersStatement for Loop {
fn has_scope(&self) -> bool {
true
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::r#loop::Loop {
inner: self.inner.compile(info, comp)?,
}))
}
}

View File

@ -0,0 +1,68 @@
use std::{collections::HashMap, fmt::Debug};
use crate::info;
pub mod assign_to;
pub mod block;
pub mod chain;
pub mod function;
pub mod r#if;
pub mod init_to;
pub mod r#loop;
pub mod switch;
pub mod tuple;
pub mod value;
pub mod variable;
pub trait MersStatement: Debug {
fn has_scope(&self) -> bool;
fn compile_custom(
&self,
info: &mut Info,
comp: CompInfo,
) -> Result<Box<dyn super::run::MersStatement>, String>;
fn compile(
&self,
info: &mut Info,
comp: CompInfo,
) -> Result<Box<dyn super::run::MersStatement>, String> {
if self.has_scope() {
info.create_scope();
}
let o = self.compile_custom(info, comp);
if self.has_scope() {
info.end_scope();
}
o
}
}
#[derive(Clone, Copy)]
pub struct CompInfo {
is_init: bool,
}
impl Default for CompInfo {
fn default() -> Self {
Self { is_init: false }
}
}
pub type Info = info::Info<Local>;
#[derive(Default, Clone, Debug)]
pub struct Local {
vars: HashMap<String, (usize, usize)>,
}
impl info::Local for Local {
type VariableIdentifier = String;
type VariableData = (usize, usize);
fn init_var(&mut self, id: Self::VariableIdentifier, value: Self::VariableData) {
self.vars.insert(id, value);
}
fn get_var(&self, id: &Self::VariableIdentifier) -> Option<&Self::VariableData> {
self.vars.get(id)
}
fn get_var_mut(&mut self, id: &Self::VariableIdentifier) -> Option<&mut Self::VariableData> {
self.vars.get_mut(id)
}
}

View File

@ -0,0 +1,27 @@
use crate::data::Type;
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Switch {
source: Box<dyn MersStatement>,
arms: Vec<SwitchArm>,
}
#[derive(Debug)]
pub struct SwitchArm {
requires_type: Type,
}
impl MersStatement for Switch {
fn has_scope(&self) -> bool {
true
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn crate::program::run::MersStatement>, String> {
todo!()
}
}

View File

@ -0,0 +1,26 @@
use crate::{info, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Tuple {
pub elems: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Tuple {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::tuple::Tuple {
elems: self
.elems
.iter()
.map(|v| v.compile(info, comp))
.collect::<Result<Vec<_>, _>>()?,
}))
}
}

View File

@ -0,0 +1,21 @@
use crate::{data::Data, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Value(pub Data);
impl MersStatement for Value {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
Ok(Box::new(program::run::value::Value {
val: self.0.clone(),
}))
}
}

View File

@ -0,0 +1,38 @@
use crate::{info::Local, program};
use super::{CompInfo, MersStatement};
#[derive(Debug)]
pub struct Variable {
pub is_ref: bool,
pub var: String,
}
impl MersStatement for Variable {
fn has_scope(&self) -> bool {
false
}
fn compile_custom(
&self,
info: &mut crate::info::Info<super::Local>,
comp: CompInfo,
) -> Result<Box<dyn program::run::MersStatement>, String> {
if comp.is_init {
info.init_var(
self.var.clone(),
(
info.scopes.len() - 1,
info.scopes.last().unwrap().vars.len(),
),
)
}
Ok(Box::new(program::run::variable::Variable {
is_ref: comp.is_init || self.is_ref,
var: if let Some(v) = info.get_var(&self.var) {
*v
} else {
return Err(format!("No variable named '{}' found!", self.var));
},
}))
}
}

View File

@ -0,0 +1,21 @@
use crate::data;
use super::MersStatement;
#[derive(Debug)]
pub struct AssignTo {
pub target: Box<dyn MersStatement>,
pub source: Box<dyn MersStatement>,
}
impl MersStatement for AssignTo {
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
let source = self.source.run(info);
let target = self.target.run(info);
data::defs::assign(source, &target);
target
}
fn has_scope(&self) -> bool {
false
}
}

View File

@ -0,0 +1,20 @@
use std::sync::Arc;
use super::MersStatement;
#[derive(Debug)]
pub struct Block {
pub statements: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Block {
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
self.statements
.iter()
.map(|s| s.run(info))
.last()
.unwrap_or_else(|| crate::data::Data::new(crate::data::tuple::Tuple(vec![])))
}
fn has_scope(&self) -> bool {
true
}
}

View File

@ -0,0 +1,26 @@
use std::{any::Any, sync::Arc};
use crate::data::{function::Function, Data};
use super::MersStatement;
#[derive(Debug)]
pub struct Chain {
pub first: Box<dyn MersStatement>,
pub chained: Box<dyn MersStatement>,
}
impl MersStatement for Chain {
fn run_custom(&self, info: &mut super::Info) -> Data {
let f = self.first.run(info);
let c = self.chained.run(info);
let c = c.get();
if let Some(func) = c.as_any().downcast_ref::<crate::data::function::Function>() {
func.run(f)
} else {
todo!("err: not a function");
}
}
fn has_scope(&self) -> bool {
false
}
}

View File

@ -0,0 +1,17 @@
use crate::data::{self, Data, MersData};
use super::MersStatement;
#[derive(Debug)]
pub struct Function {
pub func_no_info: data::function::Function,
}
impl MersStatement for Function {
fn has_scope(&self) -> bool {
false
}
fn run_custom(&self, info: &mut super::Info) -> Data {
Data::new(self.func_no_info.with_info(info.clone()))
}
}

18
mers_lib/src/program/run/if.rs Executable file
View File

@ -0,0 +1,18 @@
use super::MersStatement;
#[derive(Debug)]
pub struct If {
pub condition: Box<dyn MersStatement>,
pub on_true: Box<dyn MersStatement>,
pub on_false: Option<Box<dyn MersStatement>>,
}
impl MersStatement for If {
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
self.condition.run(info);
todo!("what now?")
}
fn has_scope(&self) -> bool {
true
}
}

View File

@ -0,0 +1,21 @@
use crate::data::MersData;
use super::MersStatement;
#[derive(Debug)]
pub struct Loop {
pub inner: Box<dyn MersStatement>,
}
impl MersStatement for Loop {
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
loop {
if let Some(break_val) = self.inner.run(info).get().matches() {
break break_val;
}
}
}
fn has_scope(&self) -> bool {
true
}
}

63
mers_lib/src/program/run/mod.rs Executable file
View File

@ -0,0 +1,63 @@
use std::sync::Arc;
use crate::{
data::{self, Data, Type},
info,
};
pub mod assign_to;
pub mod block;
pub mod chain;
pub mod function;
pub mod r#if;
pub mod r#loop;
pub mod switch;
pub mod tuple;
pub mod value;
pub mod variable;
pub trait MersStatement: std::fmt::Debug {
fn run_custom(&self, info: &mut Info) -> Data;
/// if true, local variables etc. will be contained inside their own scope.
fn has_scope(&self) -> bool;
// fn outputs(&self) -> Type;
fn run(&self, info: &mut Info) -> Data {
if self.has_scope() {
info.create_scope();
}
let o = self.run_custom(info);
if self.has_scope() {
info.end_scope();
}
o
}
}
pub type Info = info::Info<Local>;
#[derive(Default, Clone, Debug)]
pub struct Local {
vars: Vec<Data>,
}
impl info::Local for Local {
type VariableIdentifier = usize;
type VariableData = Data;
fn init_var(&mut self, id: Self::VariableIdentifier, value: Self::VariableData) {
while self.vars.len() <= id {
self.vars.push(Data::new(data::bool::Bool(false)));
}
self.vars[id] = value;
}
fn get_var(&self, id: &Self::VariableIdentifier) -> Option<&Self::VariableData> {
match self.vars.get(*id) {
Some(v) => Some(v),
None => None,
}
}
fn get_var_mut(&mut self, id: &Self::VariableIdentifier) -> Option<&mut Self::VariableData> {
match self.vars.get_mut(*id) {
Some(v) => Some(v),
None => None,
}
}
}

View File

@ -0,0 +1,23 @@
use crate::data::Type;
use super::MersStatement;
#[derive(Debug)]
pub struct Switch {
source: Box<dyn MersStatement>,
arms: Vec<SwitchArm>,
}
#[derive(Debug)]
pub struct SwitchArm {
requires_type: Type,
}
impl MersStatement for Switch {
fn has_scope(&self) -> bool {
true
}
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
todo!("switch")
}
}

View File

@ -0,0 +1,18 @@
use crate::data::{self, Data};
use super::MersStatement;
#[derive(Debug)]
pub struct Tuple {
pub elems: Vec<Box<dyn MersStatement>>,
}
impl MersStatement for Tuple {
fn run_custom(&self, info: &mut super::Info) -> crate::data::Data {
Data::new(data::tuple::Tuple(
self.elems.iter().map(|s| s.run(info)).collect(),
))
}
fn has_scope(&self) -> bool {
false
}
}

View File

@ -0,0 +1,17 @@
use crate::data::{Data, MersData};
use super::MersStatement;
#[derive(Debug)]
pub struct Value {
pub val: Data,
}
impl MersStatement for Value {
fn has_scope(&self) -> bool {
false
}
fn run_custom(&self, info: &mut super::Info) -> Data {
self.val.clone()
}
}

View File

@ -0,0 +1,30 @@
use crate::data::{self, Data};
use super::MersStatement;
#[derive(Debug)]
pub struct Variable {
pub is_ref: bool,
pub var: (usize, usize),
}
impl MersStatement for Variable {
fn has_scope(&self) -> bool {
false
}
fn run_custom(&self, info: &mut super::Info) -> Data {
while info.scopes[self.var.0].vars.len() <= self.var.1 {
info.scopes[self.var.0]
.vars
.push(Data::new(data::bool::Bool(false)));
}
if self.is_ref {
Data::new(data::reference::Reference(
info.scopes[self.var.0].vars[self.var.1].clone(),
))
} else {
// Full-Clones!
Data::new_boxed(info.scopes[self.var.0].vars[self.var.1].get().clone())
}
}
}

26
mers_old/Cargo.toml Executable file
View File

@ -0,0 +1,26 @@
[package]
name = "mers"
version = "0.2.3"
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"

View File

@ -1463,12 +1463,12 @@ impl BuiltinFunction {
}
}),
Self::Insert => args[0].run(info).operate_on_data_mut(|v| {
args[1].run(info).operate_on_data_immut(|i| {
args[2].run(info).operate_on_data_immut(|i| {
// TODO: find out why the fuck this helps
if let (VDataEnum::Reference(v), VDataEnum::Int(i)) = (v, i) {
v.operate_on_data_mut(|v| {
if let VDataEnum::List(_, v) = v {
v.insert(*i as _, args[2].run(info));
v.insert(*i as _, args[1].run(info));
}
});
VDataEnum::Tuple(vec![]).to()

245
mers_old/src/main.rs Executable file
View File

@ -0,0 +1,245 @@
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 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();
}
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);
}
}
}