diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-08-04 23:50:46 +0300 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-08-04 23:50:46 +0300 |
commit | fdae943090463526423f5e43e72cd2f0e8147a1b (patch) | |
tree | 5a7212555d8511df16fe6bbcc54b863ec9463b46 | |
parent | 36d2818d39e61b752923e253f8455f50510cb428 (diff) | |
download | myslip-fdae943090463526423f5e43e72cd2f0e8147a1b.tar.gz myslip-fdae943090463526423f5e43e72cd2f0e8147a1b.zip |
Added repl and some documentation. Improved error messages. Removed dead code.
* Removed same_variant in parse::parsetree
* Added SExp::multistep (for use of the repl)
Improved error messages:
* Added parenthesis around types
* Changed how errors propagate inferring generics:
added the error variant ArgumentsDontMatchGeneric,
implemented the displaying of it, added tests for
it.
* Had to change some tests to match for the new changes
-rw-r--r-- | README.md | 63 | ||||
-rw-r--r-- | TUTORIAL.md | 84 | ||||
-rw-r--r-- | src/main.rs | 63 | ||||
-rw-r--r-- | src/parse/parsetree.rs | 15 | ||||
-rw-r--r-- | src/sexp/display.rs | 25 | ||||
-rw-r--r-- | src/sexp/step.rs | 8 | ||||
-rw-r--r-- | src/type/check.rs | 17 | ||||
-rw-r--r-- | src/type/display.rs | 20 | ||||
-rw-r--r-- | src/type/mod.rs | 5 |
9 files changed, 260 insertions, 40 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..55a1c9a --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ + + +myslip README +============= + +Below is a short introduction to this programming language +and instructions on setting it up. +`TUTORIAL.md` may help getting familiar with the language. + + +Language introduction +--------------------- +STILL TODO (or to modify) +how functional is it? +myslip is a lisp-inspired language, but a bit different +(mostly, because I have never really written lisp except + for in emacs, so I have no clue of what lisp usually is + like). +Valid myslip s-expressions may be atoms of different types +or lists of them, though not all syntactically valid +myslip expressions are ones that can be evaluated. + +S-expression := Atom | List +Atom := TODO +List := (S-expression ... S-expression) + +The myslip interpreter performs type checking on +s-expressions and evaluates them according to polish +notation, ie. the first S-expression of a list is taken to +be the operator that is passed the rest of the expressions +as arguments. + + +Running the project +------------------- + +myslip can be built using cargo 1.88.0. Instructions to +install cargo can be found online: +https://doc.rust-lang.org/cargo/getting-started/installation.html + +If you are having trouble due to having a different version +and you used rustup for your cargo installation, you can +follow these instructions on how to change the rust version +https://rust-lang.github.io/rustup/overrides.html + +TODO: system requirements? try it out on debian +```console +TODO +$ sudo apt install curl git +$ curl ??? | sh ??? +$ git clone ??? +$ cd myslip +``` +and then the interpreter can be accessed either via +`cargo run`, or then you can add it to your path: +```console +TODO +$ cargo build +$ ln ??? ??? +$ myslip +``` + + diff --git a/TUTORIAL.md b/TUTORIAL.md new file mode 100644 index 0000000..7389d55 --- /dev/null +++ b/TUTORIAL.md @@ -0,0 +1,84 @@ + + +Tutorial +======== + +This tutorial consists of a language tour +and a set of exercises, in the order of +mentioning. + + +Tour +---- + + +**Arithmetic operations** + +Addition, multiplication, subtraction and division +are supported with +, *, - and / respectively. +```console +> (+ 1 1) +2 : Int +> (* 2 3) +6 : Int +> (- 5 4) +1 : Int +> (/ 4 2) +2 : Int +``` + +Decimals are truncated in division: +```console +> (/ 5 2) +2 : Int +``` + + +**Lists** + +Lists in myslip correspond to what is known as tuples in many +other programming languages. This difference exists because +myslip is a list processor, and not using this underlying +construct of the language would be odd. + +In principle, +```myslip +(1 2 3 4 5) +``` +is a valid list, but it is evaluated by the interpreter, +which assumes that the first term, `1`, is an operator. +That's why constructing a list requires the operator +`quote`: +```console +> quote +quote : (T -> T) +> (quote 1 2 3 4 5) +(quote 1 2 3 4 5) : (Int Int Int Int Int) +``` + +As hinted above, `quote` is typewise the identity function. +Behaviorally it does though simplify sub-expressions. +```console +> (quote (+ 1 1) (+ 2 2)) +(quote 2 4) : (Int Int) +``` + +The elements of a list can of course be of different types: +```console +> (quote + 0 (quote 1 2)) +(quote + 0 (quote 1 2)) : (((Int Int) -> Int) Int (Int Int)) +``` + +TODO: List destructuring + + +**Understanding error messages** +TODO: div zero +TODO: unclosed parenthesis +TODO: type errors + + +Exercises +--------- + +TODO diff --git a/src/main.rs b/src/main.rs index e7a11a9..80a4066 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,64 @@ + +use melisp::parse::parsetree::parse_to_ast; +use melisp::sexp::{SExp::*, SLeaf::Nil}; +use std::{io, io::Write}; + fn main() { - println!("Hello, world!"); + match repl() { + Ok(()) => println!("bye :)"), + Err(e) => { + println!("Error: {}", e); + main(); + }, + } } + +fn repl() -> Result<(), String> { + + let stdin = io::stdin(); + + let mut input = String::new(); + + let mut stdout = io::stdout(); + + print!("> "); + match stdout.flush() { + Ok(_) => (), + Err(_) => println!("Enter s-expression:"), + }; + let mut success = stdin.read_line(&mut input); + + while success.is_ok() && input != "exit\n" { + if let Ok(0) = success { + print!("\n"); + break; + } + + let expression = match parse_to_ast(&input) { + Ok(SCons(a, b)) if *b == Atom(Nil) => Ok(*a), + t => t + }?; + + let ty = expression.type_check().map_err(|e| e.to_string())?; + + let result = expression.multistep()?; + + println!("{} : {}", result, ty); + + print!("> "); + match stdout.flush() { + Ok(_) => (), + Err(_) => println!("Enter s-expression:"), + }; + input.clear(); + success = stdin.read_line(&mut input); + + } + + match success { + Ok(_) => Ok(()), + Err(e) => Err(format!("read error: {e}")), + } + +} + diff --git a/src/parse/parsetree.rs b/src/parse/parsetree.rs index 7e1c80d..5507b0b 100644 --- a/src/parse/parsetree.rs +++ b/src/parse/parsetree.rs @@ -21,19 +21,6 @@ pub enum Token { Whitespace(String), } -impl Token { - fn same_variant(&self, other: Token) -> bool { - match (self, other) { - (ParOpen, ParOpen) => true, - (ParClose, ParClose) => true, - (Num(_), Num(_)) => true, - (Sym(_), Sym(_)) => true, - (Whitespace(_), Whitespace(_)) => true, - _ => false - } - } -} - use std::fmt; use std::fmt::Display; impl Display for Token { @@ -88,8 +75,6 @@ fn tokens_to_ast_inner( mut input: VecDeque<Token> ) -> Result<(Vec<Token>, SExp), String> { - println!("{:?}", input.clone()); - let mut current_token = input.pop_front(); match current_token { diff --git a/src/sexp/display.rs b/src/sexp/display.rs index e6dbb7c..238e8d4 100644 --- a/src/sexp/display.rs +++ b/src/sexp/display.rs @@ -26,20 +26,17 @@ impl fmt::Display for SExp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Atom(leaf) => write!(f, "{}", leaf.to_string()), - SCons(a, b) => { - match scons(a.clone(), b.clone()).into_vec() { - Ok(l) => write!( - f, - "({})", - l.into_iter() - .map(|x| x.to_string()) - .collect::<Vec<String>>() - .join(" ") - ), - Err(_) => write!(f, "({} {})", *a, *b), - } - } + SCons(a, b) => + write!( + f, + "({})", + scons(a.clone(), b.clone()) + .parts() + .into_iter() + .map(|x| x.to_string()) + .collect::<Vec<String>>() + .join(" ") + ) } } - } diff --git a/src/sexp/step.rs b/src/sexp/step.rs index 765e33c..1ab0291 100644 --- a/src/sexp/step.rs +++ b/src/sexp/step.rs @@ -209,4 +209,12 @@ impl SExp { t => Err(format!("unimplemented: {:?}.step()", t)), } } + + + pub fn multistep(self) -> Result<Self, String> { + match self.clone().step()? { + x if x == self => Ok(self), + x => x.multistep() + } + } } diff --git a/src/type/check.rs b/src/type/check.rs index 647ec4d..80cc974 100644 --- a/src/type/check.rs +++ b/src/type/check.rs @@ -192,7 +192,14 @@ impl Type { match self { Arrow(from, to) => { - let generics = (*from).infer_generics_ctx(argtype, Vec::new())?; + let generics = match (*from).infer_generics_ctx(argtype, Vec::new()) { + Ok(x) => Ok(x), + Err(None) => Err(ArgumentsDontMatchGeneric { + argtype: argtype.clone(), + generictype: self.clone(), + }), + Err(Some(e)) => Err(e), + }?; let mut restype = (**to).clone(); for (name, ty) in generics { restype = restype.subst(&name, &ty); @@ -211,7 +218,7 @@ impl Type { &self, argtype: &Type, ctx: Vec<(String, Type)> - ) -> Result<Vec<(String, Type)>, TypeError> { + ) -> Result<Vec<(String, Type)>, Option<TypeError>> { match (self, argtype) { (a, b) if a == b => Ok(ctx), @@ -239,11 +246,7 @@ impl Type { Ok(res) }, - (_a, _b) => Err(InvalidArgList { - arglist: Atom(Var("undefined".to_string())), // TODO: hacky as heck - expected: self.clone(), - found: argtype.clone(), - }), + (_a, _b) => Err(None), } } diff --git a/src/type/display.rs b/src/type/display.rs index be51858..68d7fec 100644 --- a/src/type/display.rs +++ b/src/type/display.rs @@ -11,13 +11,13 @@ impl fmt::Display for Type { /// ```rust /// use melisp::r#type::{Type::*, util::*}; /// assert_eq!(Integer.to_string(), "Int".to_string()); - /// assert_eq!(arr(Integer, Integer).to_string(), "Int -> Int".to_string()); + /// assert_eq!(arr(Integer, Integer).to_string(), "(Int -> Int)".to_string()); /// assert_eq!(List(vec![Integer, Integer, Integer]).to_string(), "(Int Int Int)".to_string()); /// ``` fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Integer => write!(f, "{}", "Int"), - Arrow(a, b) => write!(f, "{} -> {}", a, b), + Arrow(a, b) => write!(f, "({} -> {})", a, b), List(types) => write!( f, "({})", @@ -50,7 +50,7 @@ impl fmt::Display for TypeError { /// expected: arr(VarType("?".to_string()), VarType("?".to_string())), /// found: Integer /// }.to_string(), - /// "invalid operator: '1'\nexpected: '? -> ?'\nfound: 'Int'".to_string() + /// "invalid operator: '1'\nexpected: '(? -> ?)'\nfound: 'Int'".to_string() /// ); /// assert_eq!( /// InvalidArgList { @@ -64,6 +64,13 @@ impl fmt::Display for TypeError { /// UnboundGeneric(String::from("?")).to_string(), /// "unbound generic type: '?'".to_string() /// ); + /// assert_eq!( + /// ArgumentsDontMatchGeneric { + /// argtype: Integer, + /// generictype: arr(VarType("T".to_string()), Integer) + /// }.to_string(), + /// "incompatible argument type 'Int' and generic operator type '(T -> Int)'".to_string() + /// ); /// ``` fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -83,6 +90,13 @@ impl fmt::Display for TypeError { arglist, expected, found ) }, + ArgumentsDontMatchGeneric { argtype, generictype } => { + write!( + f, + "incompatible argument type '{}' and generic operator type '{}'", + argtype, generictype + ) + }, OtherError => write!(f, "uncategorized error"), } } diff --git a/src/type/mod.rs b/src/type/mod.rs index aeeff93..2454f31 100644 --- a/src/type/mod.rs +++ b/src/type/mod.rs @@ -45,6 +45,11 @@ pub enum TypeError { found: Type, }, + ArgumentsDontMatchGeneric { + argtype: Type, + generictype: Type, + }, + OtherError } |