├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── guide.md └── reference.md ├── examples ├── abacaba.paste ├── collatz.paste ├── fac.paste ├── fib.paste ├── fizzbuzz.paste ├── gcd.paste ├── hello.paste ├── newton.paste └── prime.paste └── src ├── eval.rs ├── lex.rs ├── lib.rs ├── main.rs ├── parse.rs └── std.paste /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 8 | 9 | [[package]] 10 | name = "noisy_float" 11 | version = "0.1.13" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "a14c16cde392a1cd18084ffd8348cb8937525130e62f0478d72dcc683698809d" 14 | dependencies = [ 15 | "num-traits", 16 | ] 17 | 18 | [[package]] 19 | name = "num-traits" 20 | version = "0.2.12" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 23 | dependencies = [ 24 | "autocfg", 25 | ] 26 | 27 | [[package]] 28 | name = "paste-lang" 29 | version = "0.1.0" 30 | dependencies = [ 31 | "noisy_float", 32 | "pico-args", 33 | ] 34 | 35 | [[package]] 36 | name = "pico-args" 37 | version = "0.4.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d70072c20945e1ab871c472a285fc772aefd4f5407723c206242f2c6f94595d6" 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paste-lang" 3 | version = "0.1.0" 4 | authors = ["slerpy <48803074+slerpyyy@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | [profile.release] 8 | opt-level = 3 9 | lto = true 10 | panic = "abort" 11 | 12 | [dependencies] 13 | noisy_float = "0.1.13" 14 | pico-args = "0.4.0" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aaron Bies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paste 2 | *An esoteric programming language build around macros* 3 | 4 | Paste is a dynamically typed, stack based esoteric programming language build around macros, which are defined and applied at runtime. 5 | 6 | ```hs 7 | (fib =' ;{ 8 | ;n = 9 | 0 1 10 | (n >' 0) ;{ 11 | xch over + 12 | (;n =' (n -' 1)) 13 | (n !=' 0) 14 | } while 15 | pop 16 | }) 17 | (put fib 24) 18 | ``` 19 | (the program above prints out the 24th fibonacci number) 20 | 21 | ## The Language 22 | 23 | If you are interested in playing around with Paste, I highly recommend you check out my [Guide to Paste](docs/guide.md) for an overview to what this language has to offer. 24 | 25 | ## Usage 26 | 27 | Here is a short description of what the interpreter can do. You can also get this information from the program directly, by using the `--help` flag. 28 | ``` 29 | Usage: paste [OPTIONS] INPUT 30 | 31 | An esoteric programming language build around macros 32 | 33 | Options: 34 | -h, --help Print this help message and exit. 35 | -r, --repl Enter interactive mode after the script terminated. 36 | -d, --debug Show all internal computations during evaluation. 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/guide.md: -------------------------------------------------------------------------------- 1 | # Guide to Paste 2 | 3 | *A quick guide to the Paste programming language* 4 | 5 | ```hs 6 | (gcd =' ;{ 7 | 1 ;{ 8 | (copy 2) < ;xch if 9 | over xch - 10 | (0 !=' over) 11 | } while 12 | xch pop 13 | }) 14 | (put gcd 35 91) 15 | ``` 16 | 17 | ## Debug Mode 18 | 19 | The Paste interpreter comes with a build-in debug mode, which can be enabled using the `--debug` (or just `-d`) flag in the command line. With debug mode enabled, the interpreter outputs its internal state in every step of the evaluation. This includes the current state of the stack, as well as the part of the program, which is yet to be evaluated. 20 | 21 | ```hs 22 | > 3 5 min 23 | ~> | 3 5 min 24 | ~> 3 | 5 min 25 | ~> 3 5 | min 26 | ~> 3 5 | 2 copy < ;xch if pop 27 | ~> 3 5 2 | copy < ;xch if pop 28 | ~> 3 5 3 5 | < ;xch if pop 29 | ~> 3 5 0 | ;xch if pop 30 | ~> 3 5 0 xch | if pop 31 | ~> 3 5 | pop 32 | ~> 3 5 | ;_ = 33 | ~> 3 5 _ | = 34 | ~> 3 | 35 | ``` 36 | 37 | ## Symbols 38 | 39 | In Paste, everything is a symbol. A symbol may of may not have special meaning attached to it. If a symbol represents an operation or a macro, it is evaluated eagerly. If a symbol has no meaning assigned to it, it is simply placed on the stack during evaluation. Eager evaluation can be deferred, by adding a `;` at the beginning of a token. The `;` itself is not part of the token. 40 | 41 | ```py 42 | 1 2 + # stack: 3 43 | 4 ;+ # stack: 3 4 + 44 | do # stack: 7 45 | ``` 46 | 47 | The `;` can also be chained. 48 | 49 | ```hs 50 | "Test" ;;put do ;;do ;do do do 51 | ``` 52 | 53 | ## The Stack 54 | 55 | Paste is a stack based language. Every symbol either puts itself onto the stack or does an operation on the stack. Here is a simple example program: 56 | 57 | ```hs 58 | 2 1 3 + * 9 - 59 | ``` 60 | 61 | Let's look at it in a bit more detail: 62 | 63 | ```py 64 | 2 # 2 puts the symbol 2 onto the stack 65 | 1 # 2 1 pushes the symbol 1 onto the stack 66 | 3 # 2 1 3 pushes 3 onto the stack 67 | + # 2 4 pops 2 symbols, adds them together and pushes the result onto the stack 68 | * # 8 pops 2 symbols and multiplies them together 69 | 9 # 8 9 pushes 1 onto the stack 70 | - # 1 pops 2 symbols and subtracts the first from the second 71 | ``` 72 | 73 | To make working with the stack a bit more comfortable, the following operations and macros are provided by default: 74 | 75 | ```py 76 | dup # a -- a a 77 | pop # a -- 78 | xch # a b -- b a 79 | over # a b -- a b a 80 | under # a b -- b 81 | ``` 82 | 83 | ## Macros 84 | 85 | Macros are defined using the `=` operator, which pops two symbols off the stack and assigns the first to the second. 86 | 87 | ```py 88 | 5 n = # n is now 5 89 | n put # prints "5" 90 | ``` 91 | 92 | **Note:** This works with every\* pair of symbols. 93 | 94 | ```py 95 | 2 4 = # 4 is now 2 96 | 1 4 + # calculates 1 + 4, which is 3, because 4 is 2 97 | put # prints "3" 98 | ``` 99 | 100 | Since macros are applied sequentially, circularity is no concern: 101 | 102 | ```py 103 | 7 5 = # 7 is now 5 104 | 5 7 = # sets 5 to 5, because 7 is 5 105 | ``` 106 | 107 | \*The symbol `_` (pronounced "meh") cannot be used as a macro. Attempting to assign a macro to `_` is always equivalent to simply popping a symbol off the stack. 108 | 109 | ## Blocks 110 | 111 | Blocks are symbols which contain other symbols. Like every other symbol, they are eagerly evaluated by default. In combination with `;` and these helper symbols, block symbols are one of the most powerful features of the language: 112 | 113 | - `do` pops and evaluated a symbol 114 | - `if` pops a number and am symbol and only runs the symbol, if the number if different to zero 115 | - `while` works like `if`, but loops until the popped condition is zero 116 | 117 | ```py 118 | { "Hello World" put } # always prints "Hello World" 119 | n 5 == ;{ "n is five" put } if # only prints, when n is equal to 5 120 | 0 1 1 1 ;{ "Hi" put } while # prints "Hi" 3 times 121 | 1 ;{ "A" put 1 } while # infinite loop 122 | ``` 123 | 124 | Block symbols can be given names using macros to simulate function-like behavior: 125 | 126 | ```py 127 | ;{ 2 copy > ;{ xch } if pop } ;max = # defines a max "function" 128 | 5 2 max # calculates max of both numbers 129 | put # prints "5" 130 | ``` 131 | 132 | Named block symbols can also be used to create loops: 133 | 134 | ```py 135 | ;{ dup put ;b if } b = # defines a block named "b" 136 | 0 1 2 3 4 b # prints the numbers 4 to 0 137 | ``` 138 | 139 | ## Syntax Sugar 140 | 141 | Since paste is a stack based language, the token order you naturally end up with is [reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation). 142 | To make working with function-like macros a little more intuitive, there is an alternative to the normal code blocks, which uses parenthesis instead of curly brackets. 143 | These alternative blocks reverse the order of their containing symbols during parsing, effectively simulating polish notation: 144 | 145 | ```py 146 | (quit) ~> {quit} 147 | (put "hello") ~> {"hello" put} 148 | (+ 4 3) ~> {3 4 +} 149 | (put mod 7 3) ~> {3 7 mod put} 150 | ``` 151 | 152 | Additionally, a tick `'` can be used to move a symbol to the beginning of a block. This reordering step is applied before the above mentioned token reversal, which makes working with binary operators very comfortable: 153 | 154 | ```hs 155 | (a =' 3) ~> (= a 3) ~> {3 a =} 156 | (b =' (a +' 2)) ~> (= b (+ a 2)) ~> {{2 a +} b =} 157 | ``` 158 | -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- 1 | # List of native operations 2 | 3 | | Usage | Meaning | 4 | |---|---| 5 | | val name `=` | Assigns `name` the meaning of `val` if `name` is not equal to `_` | 6 | | symbol `do` | Evaluated `symbol` | 7 | | cond then else `?` | Evaluated `then` if `cond` is true, and `else` otherwise | 8 | | cond stmt `while` | If `cond` is true, it evaluates `stmt ;stmt while`, and does nothing otherwise | 9 | | num `copy` | Copies the last `num` symbols on the stack | 10 | | symbol `put` | Prints a symbol | 11 | | y x `+` | Adds `x` and `y` together | 12 | | y x `-` | Subtracts `y` from `x` | 13 | | y x `*` | Multiplies `x` and `y` | 14 | | y x `/` | Divides `x` by `y` | 15 | | y x `==` | Returns 1 if x and y are equal, and 0 otherwise | 16 | | y x `<` | Returns 1 if `x` is less than `y`, and 0 otherwise | 17 | | f `floor` | Converts a float `f` into an int by flooring it | 18 | | code `exit` | Terminates the program with a given exit code or `-1` if the stack is empty | 19 | -------------------------------------------------------------------------------- /examples/abacaba.paste: -------------------------------------------------------------------------------- 1 | (abacaba =' ;{ 2 | ;n = 3 | (n <' 2) 4 | ;(put n) 5 | ;{ 6 | n 7 | (n -' 1) abacaba 8 | ;n = 9 | (put n) 10 | (n -' 1) abacaba 11 | } ? 12 | }) 13 | 14 | (abacaba 7) 15 | -------------------------------------------------------------------------------- /examples/collatz.paste: -------------------------------------------------------------------------------- 1 | (collatz =' ;{ 2 | dup put ;n = 3 | 4 | (;"col" +' n) dup dup do != 5 | ;do ;{ 6 | (n ==' ((n /' 2) *' 2)) 7 | ;(n /' 2) ;((3 *' n) +' 1) ? 8 | xch over xch = 9 | } ? 10 | 11 | (n >' 1) ;{ 12 | " -> " put 13 | collatz 14 | } ;pop ? 15 | }) 16 | 17 | (collatz 100) 18 | -------------------------------------------------------------------------------- /examples/fac.paste: -------------------------------------------------------------------------------- 1 | (fac =' ;{ 2 | ;n = 3 | (n >' 1) ;{ 4 | n (n -' 1) fac * 5 | } ;1 ? 6 | }) 7 | 8 | (put fac 13) 9 | -------------------------------------------------------------------------------- /examples/fib.paste: -------------------------------------------------------------------------------- 1 | (;f0 =' 0) 2 | (;f1 =' 1) 3 | 4 | (fib =' ;{ 5 | ;n = 6 | (;f +' n) dup dup do != 7 | ;do ;{ 8 | (n -' 1) (n -' 2) 9 | fib xch fib + 10 | xch over xch = 11 | } ? 12 | }) 13 | 14 | (put fib 42) 15 | -------------------------------------------------------------------------------- /examples/fizzbuzz.paste: -------------------------------------------------------------------------------- 1 | 1 2 | 3 | (fizzbuzz =' ;{ 4 | 0 5 | over 3 xch mod 0 == dup ;(put "Fizz") if + 6 | over 5 xch mod 0 == dup ;(put "Buzz") if + 7 | ! ;{dup put} if " " put 8 | 9 | 1 + dup 100 >= 10 | ;fizzbuzz if 11 | }) 12 | 13 | fizzbuzz 14 | -------------------------------------------------------------------------------- /examples/gcd.paste: -------------------------------------------------------------------------------- 1 | (gcd =' ;{ 2 | 1 ;{ 3 | (copy 2) < ;xch if 4 | over xch - 5 | (0 !=' over) 6 | } while 7 | xch pop 8 | }) 9 | 10 | (put gcd 35 91) 11 | -------------------------------------------------------------------------------- /examples/hello.paste: -------------------------------------------------------------------------------- 1 | (put "Hello World") 2 | -------------------------------------------------------------------------------- /examples/newton.paste: -------------------------------------------------------------------------------- 1 | (sqrt =' ;{ 2 | ;n = 3 | (n /' 2.0) ;k = 4 | 5 | 0 1 dup 2 copy 4 copy ;{ 6 | (;k =' ((k +' (n /' k)) /' 2.0)) 7 | } while 8 | 9 | k 10 | }) 11 | 12 | (put sqrt 2.0) 13 | -------------------------------------------------------------------------------- /examples/prime.paste: -------------------------------------------------------------------------------- 1 | (prime =' ;{ 2 | dup 4 > ;{ # n: 0 1 2 3 4 ... 3 | 2 > # !p: 1 1 0 0 1 ... 4 | } ;{ 5 | 2 0 1 ;{ # n k _ 6 | pop # n k 7 | 2 copy xch mod 0 == # n k div 8 | xch 1 + xch # n k' div 9 | 3 copy pop dup * > # n k' div break 10 | over + ! # n k' div cond 11 | } while 12 | ;res = pop pop 13 | res 14 | } ? ! # prime = !div 15 | }) 16 | 17 | (;n =' 0) 18 | 1 ;{ 19 | (prime n) ;{ 20 | (put n) 21 | (put " ") 22 | } if 23 | 24 | (;n =' (n +' 1)) 25 | (n <' 1000) 26 | } while 27 | -------------------------------------------------------------------------------- /src/eval.rs: -------------------------------------------------------------------------------- 1 | use crate::lex::*; 2 | use crate::parse::*; 3 | use noisy_float::prelude::*; 4 | use std::{ 5 | collections::{HashMap, VecDeque}, 6 | fmt, 7 | io::{Read, Write}, 8 | process, 9 | }; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Evaluator { 13 | macros: HashMap, 14 | stack: Vec, 15 | work: VecDeque, 16 | } 17 | 18 | impl fmt::Display for Evaluator { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | for sym in &self.stack { 21 | write!(f, "{} ", sym)?; 22 | } 23 | write!(f, "|")?; 24 | for sym in &self.work { 25 | write!(f, " {}", sym)?; 26 | } 27 | Ok(()) 28 | } 29 | } 30 | 31 | macro_rules! pop_stack { 32 | ($self: ident, $($var: ident),*) => { 33 | $self.stack_assert(pop_stack!(@COUNT; $($var),*))?; 34 | $(let $var = $self.stack.pop().unwrap();)* 35 | }; 36 | 37 | (@COUNT; $($var: ident),*) => { 38 | <[()]>::len(&[$(pop_stack!(@SUBST; $var)),*]) 39 | }; 40 | 41 | (@SUBST; $_: ident) => { () }; 42 | } 43 | 44 | impl Default for Evaluator { 45 | fn default() -> Self { 46 | Self::new() 47 | } 48 | } 49 | 50 | impl Evaluator { 51 | /// Creates a new empty evaluator. 52 | #[inline] 53 | #[must_use] 54 | pub fn new() -> Self { 55 | Self { 56 | macros: HashMap::new(), 57 | stack: Vec::new(), 58 | work: VecDeque::new(), 59 | } 60 | } 61 | 62 | /// Creates a new evaluator with preloaded standard library. 63 | #[inline] 64 | #[must_use] 65 | pub fn with_std() -> Self { 66 | let mut new = Self::new(); 67 | new.load_std(); 68 | new 69 | } 70 | 71 | /// Loads and evaluated the standard library. 72 | #[inline] 73 | pub fn load_std(&mut self) { 74 | // SAFETY: The standard library does not change depending 75 | // on user input and is tested explicitly 76 | let std_lib = include_str!("std.paste"); 77 | self.extend_code(std_lib).unwrap(); 78 | } 79 | 80 | /// Extends the program to be evaluated. 81 | /// 82 | /// The program is given as a &str containing paste source code, which is 83 | /// parsed internally. 84 | /// 85 | /// # Errors 86 | /// Any errors produced during parsing are forwarded 87 | /// in the result. 88 | #[inline] 89 | pub fn extend_code(&mut self, source: &str) -> Result<(), String> { 90 | let prog = parse(lex(source))?; 91 | self.work.extend(prog); 92 | Ok(()) 93 | } 94 | 95 | /// Extends the program to be evaluated. 96 | #[inline] 97 | pub fn extend_program(&mut self, prog: Vec) { 98 | self.work.extend(prog); 99 | } 100 | 101 | // Asserts that the stack holds at least `len` elements. 102 | #[inline] 103 | fn stack_assert(&self, len: usize) -> Result<(), String> { 104 | if len > self.stack.len() { 105 | return Err(format!( 106 | "expected {} items on the stack, found {}", 107 | len, self.stack.len() 108 | )); 109 | } 110 | 111 | Ok(()) 112 | } 113 | 114 | #[inline] 115 | fn eval_native( 116 | &mut self, 117 | sym: Native, 118 | _input: &mut dyn Read, 119 | output: &mut dyn Write, 120 | ) -> Result<(), String> { 121 | match sym { 122 | Native::Assign => { 123 | pop_stack!(self, key, val); 124 | if key != val && key != Sym::Text("_".into()) { 125 | self.macros.insert(key, val); 126 | } 127 | } 128 | 129 | Native::Comma => { 130 | pop_stack!(self, x, y); 131 | self.stack.push(Sym::Block(vec![x, y])); 132 | } 133 | 134 | Native::Do => { 135 | pop_stack!(self, x); 136 | self.work.push_front(x); 137 | } 138 | 139 | Native::Defer => { 140 | pop_stack!(self, x); 141 | self.stack.push(Sym::Defer(x.into())); 142 | } 143 | 144 | Native::If => { 145 | pop_stack!(self, stmt, cond); 146 | 147 | match cond { 148 | Sym::Int(0) => (), 149 | Sym::Int(_) => self.work.push_front(stmt), 150 | _ => { 151 | self.stack.push(cond); 152 | self.stack.push(stmt); 153 | return Err("invalid condition".into()); 154 | } 155 | } 156 | } 157 | 158 | Native::While => { 159 | pop_stack!(self, stmt, cond); 160 | 161 | match cond { 162 | Sym::Int(0) => (), 163 | Sym::Int(_) => { 164 | self.work.push_front(Sym::Native(Native::While)); 165 | self.work.push_front(Sym::Defer(Box::new(stmt.clone()))); 166 | self.work.push_front(stmt); 167 | } 168 | _ => { 169 | self.stack.push(cond); 170 | self.stack.push(stmt); 171 | return Err("invalid condition".into()); 172 | } 173 | } 174 | } 175 | 176 | Native::Tern => { 177 | pop_stack!(self, else_stmt, then_stmt, cond); 178 | 179 | match cond { 180 | Sym::Int(0) => self.work.push_front(else_stmt), 181 | Sym::Int(_) => self.work.push_front(then_stmt), 182 | _ => { 183 | self.stack.push(cond); 184 | self.stack.push(then_stmt); 185 | self.stack.push(else_stmt); 186 | return Err("invalid condition".into()); 187 | } 188 | } 189 | } 190 | 191 | Native::Copy => { 192 | pop_stack!(self, num); 193 | let len = self.stack.len(); 194 | 195 | if let Sym::Int(amount) = num { 196 | if amount < 0 || amount as usize > len { 197 | self.stack.push(num); 198 | return Err("invalid copy amount".into()); 199 | } 200 | 201 | let amount = amount as _; 202 | self.stack.reserve(amount); 203 | for i in len.saturating_sub(amount)..len { 204 | self.stack.push(self.stack[i].clone()); 205 | } 206 | } else { 207 | self.stack.push(num); 208 | return Err("invalid copy".into()); 209 | } 210 | } 211 | 212 | Native::Xch => { 213 | self.stack_assert(2)?; 214 | let len = self.stack.len(); 215 | self.stack.swap(len - 2, len - 1) 216 | } 217 | 218 | Native::Put => { 219 | pop_stack!(self, sym); 220 | 221 | let string; 222 | let buffer = match &sym { 223 | Sym::Text(s) => s.as_ref(), 224 | s => { 225 | string = format!("{}", s); 226 | &string 227 | } 228 | }; 229 | 230 | output 231 | .write(buffer.as_bytes()) 232 | .map_err(|_| "failed to write to output")?; 233 | } 234 | 235 | Native::Add | Native::Sub | Native::Mul | Native::Div | Native::Eq | Native::Less => { 236 | pop_stack!(self, a, b); 237 | 238 | let c = match (sym, a, b) { 239 | (Native::Add, Sym::Int(x), Sym::Int(y)) => Sym::int(x + y), 240 | (Native::Add, Sym::Int(x), Sym::Float(y)) => Sym::Float(r64(x as _) + y), 241 | (Native::Add, Sym::Float(x), Sym::Int(y)) => Sym::Float(x + r64(y as _)), 242 | (Native::Add, Sym::Float(x), Sym::Float(y)) => Sym::Float(x + y), 243 | (Native::Add, Sym::Block(mut x), Sym::Block(y)) => Sym::Block({x.extend(y); x}), 244 | (Native::Add, Sym::Block(mut x), y) => Sym::Block({x.push(y); x}), 245 | (Native::Add, x, Sym::Block(mut y)) => Sym::Block({y.insert(0, x); y}), 246 | (Native::Add, x, y) => Sym::concat(x, y), 247 | 248 | (Native::Sub, Sym::Int(x), Sym::Int(y)) => Sym::int(x - y), 249 | (Native::Sub, Sym::Int(x), Sym::Float(y)) => Sym::Float(r64(x as _) - y), 250 | (Native::Sub, Sym::Float(x), Sym::Int(y)) => Sym::Float(x - r64(y as _)), 251 | (Native::Sub, Sym::Float(x), Sym::Float(y)) => Sym::Float(x - y), 252 | 253 | (Native::Mul, Sym::Int(x), Sym::Int(y)) => Sym::int(x * y), 254 | (Native::Mul, Sym::Int(x), Sym::Float(y)) => Sym::Float(r64(x as _) * y), 255 | (Native::Mul, Sym::Float(x), Sym::Int(y)) => Sym::Float(x * r64(y as _)), 256 | (Native::Mul, Sym::Float(x), Sym::Float(y)) => Sym::Float(x * y), 257 | 258 | (Native::Div, Sym::Int(x), Sym::Int(y)) => Sym::int(x / y), 259 | (Native::Div, Sym::Int(x), Sym::Float(y)) => Sym::Float(r64(x as _) / y), 260 | (Native::Div, Sym::Float(x), Sym::Int(y)) => Sym::Float(x / r64(y as _)), 261 | (Native::Div, Sym::Float(x), Sym::Float(y)) => Sym::Float(x / y), 262 | 263 | (Native::Eq, x, y) => Sym::int(x == y), 264 | 265 | (Native::Less, x, y) => Sym::int(x.lt(&y)), 266 | 267 | (op, a, b) => { 268 | let error = Err(format!( 269 | "operation `{}` is undefined for `{}` and `{}`", 270 | op, a, b 271 | )); 272 | self.stack.push(b); 273 | self.stack.push(a); 274 | return error; 275 | } 276 | }; 277 | 278 | self.stack.push(c); 279 | } 280 | 281 | Native::Floor => { 282 | self.stack_assert(1)?; 283 | 284 | // SAFETY: The unwrap cannot panic because the 285 | // stack_assert above fails if the stack is empty. 286 | let s = self.stack.last_mut().unwrap(); 287 | match s { 288 | Sym::Int(_) => (), 289 | Sym::Float(x) => *s = Sym::Int(x.floor().raw() as _), 290 | _ => return Err("operation is undefined".into()), 291 | } 292 | } 293 | 294 | Native::Exit => { 295 | let code = match self.stack.pop() { 296 | Some(Sym::Int(n)) => n as _, 297 | _ => -1, 298 | }; 299 | 300 | process::exit(code) 301 | } 302 | } 303 | 304 | Ok(()) 305 | } 306 | 307 | #[inline] 308 | fn macro_replace(&mut self, sym: Sym) -> Sym { 309 | if !self.macros.contains_key(&sym) || matches!(sym, Sym::Defer(_)) { 310 | return sym; 311 | } 312 | 313 | let mut next = sym.clone(); 314 | let mut iter = 0; 315 | 316 | while let Some(val) = self.macros.get(&next) { 317 | next = val.clone(); 318 | iter += 1; 319 | 320 | debug_assert_ne!(sym, next); 321 | } 322 | 323 | if iter > 1 { 324 | self.macros.insert(sym, next.clone()); 325 | } 326 | 327 | next 328 | } 329 | 330 | /// Checks if the evaluator has terminated. 331 | /// 332 | /// The function returns true if no more symbols are left 333 | /// in the work queue and false otherwise. 334 | #[inline] 335 | #[must_use] 336 | pub fn done(&self) -> bool { 337 | self.work.is_empty() 338 | } 339 | 340 | /// Advances the state of the evaluator by one step. 341 | /// 342 | /// # Errors 343 | /// This function may terminate in an error if the current symbol 344 | /// in the working queue could not be evaluated. This may happen if too 345 | /// few symbols or the wrong kind of symbols are on the stack. 346 | pub fn step(&mut self, input: &mut dyn Read, output: &mut dyn Write) -> Result<(), String> { 347 | let sym_opt = self.work.pop_front().map(|old| self.macro_replace(old)); 348 | 349 | let sym = match sym_opt { 350 | Some(s) => s, 351 | None => return Ok(()), 352 | }; 353 | 354 | match sym { 355 | Sym::Native(n) => { 356 | if let e @ Err(_) = self.eval_native(n, input, output) { 357 | self.work.push_front(sym); 358 | return e; 359 | } 360 | } 361 | Sym::Block(s) => { 362 | s.iter() 363 | .rev() 364 | .for_each(|sym| self.work.push_front(sym.clone())); 365 | } 366 | Sym::Defer(s) => self.stack.push(*s), 367 | s => self.stack.push(s), 368 | } 369 | 370 | Ok(()) 371 | } 372 | 373 | /// Runs the evaluator until the program terminates. 374 | /// 375 | /// # Errors 376 | /// This function forwards all errors from [`step`]. 377 | #[inline] 378 | pub fn run(&mut self, input: &mut dyn Read, output: &mut dyn Write) -> Result<(), String> { 379 | while !self.done() { 380 | self.step(input, output)?; 381 | } 382 | 383 | Ok(()) 384 | } 385 | 386 | /// Runs the evaluator until it terminates or the timeout is reached. 387 | /// 388 | /// # Errors 389 | /// This function forwards all errors from [`step`]. 390 | #[inline] 391 | pub fn run_with_timeout( 392 | &mut self, 393 | input: &mut dyn Read, 394 | output: &mut dyn Write, 395 | steps: usize, 396 | ) -> Result { 397 | for curr in 0..steps { 398 | if self.done() { 399 | return Ok(curr); 400 | } 401 | self.step(input, output)?; 402 | } 403 | 404 | Ok(steps) 405 | } 406 | } 407 | 408 | #[cfg(test)] 409 | mod test { 410 | use super::*; 411 | use std::io; 412 | use std::str; 413 | 414 | #[inline] 415 | fn eval_helper(code: &str, expected: &str) { 416 | let prog = parse(lex(code)).unwrap(); 417 | let mut output = Vec::new(); 418 | 419 | let mut eval = Evaluator::with_std(); 420 | eval.run(&mut io::empty(), &mut io::sink()).unwrap(); 421 | 422 | eval.extend_program(prog); 423 | eval.run_with_timeout(&mut io::empty(), &mut output, 100_000) 424 | .unwrap(); 425 | assert!(eval.done(), "Evaluator wasn't done yet\n{}", eval); 426 | 427 | let result = str::from_utf8(output.as_slice()).unwrap(); 428 | assert_eq!(result, expected); 429 | } 430 | 431 | #[test] 432 | fn eval_sanity_check() { 433 | eval_helper("", ""); 434 | } 435 | 436 | #[test] 437 | fn eval_hello() { 438 | let code = "1 ;{ \"hello\" ;put do } if"; 439 | eval_helper(code, "hello"); 440 | } 441 | 442 | #[test] 443 | fn eval_fibonacci() { 444 | let code = "\ 445 | (fib =' ;{;n = 0 1 (n >' 0) ;{xch over + (;n =' (n -' 1)) (n !=' 0)} while pop}) 446 | (put (fib 42))"; 447 | eval_helper(code, "267914296"); 448 | } 449 | 450 | #[test] 451 | fn eval_gcd() { 452 | let code = "\ 453 | (gcd =' ;{1 ;{(copy 2) < ;xch if over xch - (0 !=' over)} while xch pop}) 454 | (put (35 gcd' 91))"; 455 | eval_helper(code, "7"); 456 | } 457 | 458 | #[test] 459 | fn eval_power() { 460 | let code = "\ 461 | (pow =' ;{;n = ;k = (k >' 1) ;((n pow' (k -' 1)) *' n) ;n ?}) 462 | (1.01 pow' 100) put"; 463 | eval_helper(code, "2.7048138294215294"); 464 | } 465 | 466 | #[test] 467 | fn eval_do_do_do() { 468 | let code = "test ;;put do ;;do ;do do do"; 469 | eval_helper(code, "test"); 470 | } 471 | 472 | #[test] 473 | fn eval_do_rep_chain() { 474 | let code = "0 ;{ x put do } 10 rep do"; 475 | eval_helper(code, "xxxxxxxxxx"); 476 | } 477 | 478 | #[test] 479 | fn eval_quick_maths() { 480 | let code = "put (9 -' ((3 +' 1) *' 2))'"; 481 | eval_helper(code, "1"); 482 | } 483 | 484 | #[test] 485 | fn eval_comp_ass() { 486 | let code = "(;n =' 5) (;n +=' 3) \ 487 | (;n /=' 4) (;n *=' 3) (;n -=' 4) (put n)"; 488 | eval_helper(code, "2"); 489 | } 490 | 491 | #[test] 492 | fn eval_and_or_exhaustive() { 493 | let code = "\ 494 | (0 &&' 0) put (0 &&' 1) put (1 &&' 0) put (1 &&' 1) put \ 495 | (0 ||' 0) put (0 ||' 1) put (1 ||' 0) put (1 ||' 1) put "; 496 | eval_helper(code, "00010111"); 497 | } 498 | 499 | #[test] 500 | fn eval_and_or_lazy() { 501 | let code = "\ 502 | (0 &&' ;{a put 1}) put \ 503 | (1 &&' ;{b put 1}) put \ 504 | (0 ||' ;{c put 0}) put \ 505 | (1 ||' ;{d put 0}) put"; 506 | eval_helper(code, "0b1c01"); 507 | } 508 | 509 | #[test] 510 | fn eval_and_lazy_chain() { 511 | let code = "(n =' 5) \ 512 | ((n >' 0 put a) &&' ;(n <' 10 put b) &&' ;(n ==' 3 put c) &&' ;(1 put d)) put"; 513 | eval_helper(code, "abc0"); 514 | } 515 | 516 | #[test] 517 | fn eval_floor_simple() { 518 | let code = "0 \ 519 | (3.0 2.3 4.5 -7.3 1.7 4.999 5.0) \ 520 | { 1 ;(put dup floor) while }"; 521 | eval_helper(code, "324-81450"); 522 | } 523 | 524 | #[test] 525 | fn eval_add_all_the_things() { 526 | let code = "(+ + + + 1 ;+ 2 a (+ + 5.2 b 2)) put"; 527 | eval_helper(code, "1+2a5.2b2"); 528 | } 529 | 530 | #[test] 531 | fn eval_add_blocks() { 532 | let code = "(put + 1 + + , 2 3 ;{4 5} 6)"; 533 | eval_helper(code, "{ 1 2 3 4 5 6 }"); 534 | } 535 | 536 | #[test] 537 | fn eval_macro_simple() { 538 | let code = "(5 =' 3) (n =' 5) (put n)"; 539 | eval_helper(code, "3"); 540 | } 541 | 542 | #[test] 543 | fn eval_macro_circle() { 544 | let code = "(5 =' 3) (3 =' 5) (put 3) (put 5)"; 545 | eval_helper(code, "33"); 546 | } 547 | 548 | #[test] 549 | fn eval_macro_override() { 550 | let code = "(2 =' 1) (2 =' 3) (1 =' 2) (put 1)"; 551 | eval_helper(code, "3"); 552 | } 553 | 554 | #[test] 555 | fn eval_macro_blocks() { 556 | let code = "0 1 5 2 3 7 9 ;{ dup put ;b if } b = b"; 557 | eval_helper(code, "9732510"); 558 | } 559 | 560 | #[test] 561 | fn eval_while_loop() { 562 | let code = "0 1 1 1 1 ;(put a) while"; 563 | eval_helper(code, "aaaa"); 564 | } 565 | 566 | #[test] 567 | fn eval_twice_twice() { 568 | let code = "\ 569 | 0 5 4 (copy 2) 7 6 (copy 3) 570 | 1 ;{dup put} while"; 571 | eval_helper(code, "6746745450"); 572 | } 573 | 574 | #[test] 575 | fn eval_ternary() { 576 | let code = "\ 577 | (test =' ;{== ;a ;b ? put}) 578 | (test 3 5) (test 4 4)"; 579 | eval_helper(code, "ba"); 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/lex.rs: -------------------------------------------------------------------------------- 1 | use std::str::Chars; 2 | 3 | #[derive(Debug, PartialEq, Clone)] 4 | pub enum Token<'a> { 5 | Str(&'a str), 6 | String(String), 7 | Int(i64), 8 | Float(f64), 9 | LeftCurly, 10 | RightCurly, 11 | LeftParen, 12 | RightParen, 13 | SemiColon, 14 | Tick, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct Lexer<'a> { 19 | input: Chars<'a>, 20 | curr: Option, 21 | mark: &'a str, 22 | } 23 | 24 | impl<'a> Lexer<'a> { 25 | #[inline] 26 | fn read_next(&mut self) { 27 | self.mark = self.input.as_str(); 28 | self.curr = self.input.next(); 29 | } 30 | 31 | #[inline] 32 | fn whitespace(&mut self) { 33 | while self.curr.map_or(false, char::is_whitespace) { 34 | self.read_next(); 35 | } 36 | } 37 | 38 | #[inline] 39 | fn comments(&mut self) { 40 | while let Some('#') = self.curr { 41 | loop { 42 | if let Some('\n') = self.curr { 43 | self.whitespace(); 44 | break; 45 | } 46 | 47 | if self.curr.is_none() { 48 | break; 49 | } 50 | 51 | self.read_next(); 52 | } 53 | } 54 | } 55 | 56 | #[inline] 57 | fn single(&mut self, tok: Token<'a>) -> Option> { 58 | self.read_next(); 59 | Some(tok) 60 | } 61 | 62 | #[inline] 63 | fn munch

(&mut self, predicate: P) -> Option> 64 | where 65 | P: Fn(char) -> bool, 66 | { 67 | let start = self.mark; 68 | let mut len = 0; 69 | 70 | while let Some(ch) = self.curr { 71 | if !predicate(ch) { 72 | break; 73 | } 74 | 75 | self.read_next(); 76 | len += 1; 77 | } 78 | 79 | let text = &start[..len]; 80 | Self::num_parse(text).or_else(|| Some(Token::Str(text))) 81 | } 82 | 83 | #[inline] 84 | fn string(&mut self) -> Option> { 85 | self.read_next(); 86 | let start = self.mark; 87 | let mut len = 0; 88 | 89 | while let Some(c) = self.curr { 90 | if c == '"' { 91 | break; 92 | } 93 | 94 | if c == '\\' { 95 | self.read_next(); 96 | len += 1; 97 | } 98 | 99 | self.read_next(); 100 | len += 1; 101 | } 102 | 103 | self.read_next(); 104 | let raw = &start[..len]; 105 | if let num @ Some(_) = Self::num_parse(raw) { 106 | return num; 107 | } 108 | 109 | Some(Token::String( 110 | raw.replace("\\r", "\r") 111 | .replace("\\t", "\t") 112 | .replace("\\\\", "\\") 113 | .replace("\\'", "\'") 114 | .replace("\\\"", "\"") 115 | .replace("\\n", "\n"), 116 | )) 117 | } 118 | 119 | #[inline] 120 | fn num_parse(raw: &'a str) -> Option> { 121 | let int = raw.parse::().ok().map(Token::Int); 122 | let float = || raw.parse::().ok().map(Token::Float); 123 | int.or_else(float) 124 | } 125 | } 126 | 127 | impl<'a> Iterator for Lexer<'a> { 128 | type Item = Token<'a>; 129 | 130 | fn next(&mut self) -> Option> { 131 | self.whitespace(); 132 | self.comments(); 133 | 134 | match self.curr? { 135 | '{' => self.single(Token::LeftCurly), 136 | '}' => self.single(Token::RightCurly), 137 | '(' => self.single(Token::LeftParen), 138 | ')' => self.single(Token::RightParen), 139 | ';' => self.single(Token::SemiColon), 140 | '\'' => self.single(Token::Tick), 141 | '"' => self.string(), 142 | _ => self.munch(|ch| { 143 | !ch.is_whitespace() && !matches!(ch, '{' | '}' | '(' | ')' | '"' | ';' | '\'' | '#') 144 | }), 145 | } 146 | } 147 | } 148 | 149 | /// Lexes a given str. 150 | /// 151 | /// This function takes a &str containing paste source code and returns a 152 | /// token stream, which is ready to be parsed. 153 | /// 154 | /// # Examples 155 | /// ``` 156 | /// # use paste_lang::*; 157 | /// let mut tokens = lex("{ \"hello\" ;put do } test ="); 158 | /// 159 | /// assert_eq!(tokens.next(), Some(Token::LeftCurly)); 160 | /// assert_eq!(tokens.next(), Some(Token::String("hello".into()))); 161 | /// assert_eq!(tokens.next(), Some(Token::SemiColon)); 162 | /// assert_eq!(tokens.next(), Some(Token::Str("put"))); 163 | /// assert_eq!(tokens.next(), Some(Token::Str("do"))); 164 | /// assert_eq!(tokens.next(), Some(Token::RightCurly)); 165 | /// assert_eq!(tokens.next(), Some(Token::Str("test"))); 166 | /// assert_eq!(tokens.next(), Some(Token::Str("="))); 167 | /// assert_eq!(tokens.next(), None); 168 | /// ``` 169 | #[inline] 170 | #[must_use] 171 | pub fn lex(source: &'_ str) -> Lexer<'_> { 172 | let mut lexer = Lexer { 173 | input: source.chars(), 174 | curr: None, 175 | mark: source, 176 | }; 177 | lexer.read_next(); 178 | lexer 179 | } 180 | 181 | #[cfg(test)] 182 | mod test { 183 | use super::*; 184 | 185 | #[test] 186 | fn lex_simple() { 187 | let code = "test # abc\n { \"hello\" ;put do }"; 188 | let res = lex(code).collect::>(); 189 | assert_eq!( 190 | res, 191 | vec![ 192 | Token::Str("test"), 193 | Token::LeftCurly, 194 | Token::String("hello".into()), 195 | Token::SemiColon, 196 | Token::Str("put"), 197 | Token::Str("do"), 198 | Token::RightCurly, 199 | ] 200 | ); 201 | } 202 | 203 | #[test] 204 | fn lex_squished() { 205 | let code = "aaa;{test{hello};put do}do"; 206 | let res = lex(code).collect::>(); 207 | assert_eq!( 208 | res, 209 | vec![ 210 | Token::Str("aaa"), 211 | Token::SemiColon, 212 | Token::LeftCurly, 213 | Token::Str("test"), 214 | Token::LeftCurly, 215 | Token::Str("hello"), 216 | Token::RightCurly, 217 | Token::SemiColon, 218 | Token::Str("put"), 219 | Token::Str("do"), 220 | Token::RightCurly, 221 | Token::Str("do"), 222 | ] 223 | ); 224 | } 225 | 226 | #[test] 227 | fn lex_comments() { 228 | let code = "# comment 1\n# comment 2\naaa# comment 3\n bbb #comment 4"; 229 | let res = lex(code).collect::>(); 230 | assert_eq!(res, vec![Token::Str("aaa"), Token::Str("bbb")]); 231 | } 232 | 233 | #[test] 234 | fn lex_string_simple() { 235 | let code = "\"Hello there!\" put"; 236 | let res = lex(code).collect::>(); 237 | assert_eq!( 238 | res, 239 | vec![Token::String("Hello there!".into()), Token::Str("put")] 240 | ); 241 | } 242 | 243 | #[test] 244 | fn lex_string_escape() { 245 | let code = r#""this\tis\'\na\\\"test\"""#; 246 | let res = lex(code).collect::>(); 247 | assert_eq!(res, vec![Token::String("this\tis\'\na\\\"test\"".into())]); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | 3 | mod eval; 4 | mod lex; 5 | mod parse; 6 | 7 | pub use eval::*; 8 | pub use lex::*; 9 | pub use parse::*; 10 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | 3 | use paste_lang::*; 4 | use pico_args::Arguments; 5 | use std::{ 6 | fs, 7 | io::{self, Write}, 8 | process, str, 9 | }; 10 | 11 | const HELP_MSG: &str = " 12 | Usage: paste [OPTIONS] INPUT 13 | 14 | An esoteric programming language build around macros 15 | 16 | Options: 17 | -h, --help Print this help message and exit. 18 | -r, --repl Enter interactive mode after the script terminated. 19 | -d, --debug Show all internal computations during evaluation. 20 | "; 21 | 22 | fn main() { 23 | let mut args = Arguments::from_env(); 24 | 25 | // output help message 26 | if args.contains(["-h", "--help"]) { 27 | println!("{}", HELP_MSG); 28 | process::exit(0); 29 | } 30 | 31 | // store options 32 | let repl = args.contains(["-r", "--repl"]); 33 | let debug = args.contains(["-d", "--debug"]); 34 | 35 | // init evaluator 36 | let mut paste = Evaluator::with_std(); 37 | paste.run(&mut io::empty(), &mut io::sink()).unwrap(); 38 | 39 | // load scripts 40 | let mut no_script = true; 41 | for filename in args.finish() { 42 | let content = fs::read_to_string(filename).expect("failed to read file"); 43 | 44 | match paste.extend_code(content.as_str()) { 45 | Ok(s) => s, 46 | Err(s) => { 47 | eprintln!("Error: {}\n", s); 48 | process::exit(-1); 49 | } 50 | } 51 | 52 | no_script = false; 53 | } 54 | 55 | // always repl if no script is entered 56 | let repl = repl || no_script; 57 | 58 | loop { 59 | // evaluate 60 | let mut newline = false; 61 | while !paste.done() { 62 | if debug { 63 | println!(" ~> {}", paste); 64 | } 65 | 66 | let mut output = Vec::new(); 67 | if let Err(s) = paste.step(&mut io::empty(), &mut output) { 68 | eprintln!("Error: {}", s); 69 | eprintln!("# {}\n", paste); 70 | process::exit(-1); 71 | } 72 | 73 | if !output.is_empty() { 74 | newline = true; 75 | print!( 76 | "{}", 77 | str::from_utf8(&output).expect("failed to read output") 78 | ); 79 | } 80 | } 81 | 82 | // trailing output 83 | if debug { 84 | println!(" ~> {}", paste); 85 | } else if newline { 86 | println!(); 87 | } 88 | 89 | // finish if not in interactive mode 90 | if !repl { 91 | break; 92 | } 93 | 94 | // ask for input 95 | print!("> "); 96 | io::stdout().flush().unwrap(); 97 | let mut buffer = String::new(); 98 | 99 | loop { 100 | io::stdin() 101 | .read_line(&mut buffer) 102 | .expect("failed to read input"); 103 | 104 | if check_complete(lex(buffer.as_str())) != Ok(false) { 105 | break; 106 | } 107 | 108 | print!(". "); 109 | io::stdout().flush().unwrap(); 110 | } 111 | 112 | // append to program 113 | let tokens = lex(buffer.as_str()); 114 | let program = match parse(tokens) { 115 | Ok(s) => s, 116 | Err(s) => { 117 | eprintln!("Error: {}\n", s); 118 | process::exit(-1); 119 | } 120 | }; 121 | paste.extend_program(program); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::lex::*; 2 | use noisy_float::prelude::{r64, R64}; 3 | use std::{cmp::Ordering, collections::HashSet, fmt, rc::Rc, str::FromStr}; 4 | 5 | macro_rules! impl_native_enum { 6 | ($($tok: ident => $text: literal,)+) => { 7 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 8 | pub enum Native { 9 | $($tok,)+ 10 | } 11 | 12 | impl FromStr for Native { 13 | type Err = (); 14 | fn from_str(string: &str) -> Result { 15 | match string { 16 | $($text => Ok(Native::$tok),)+ 17 | _ => Err(()), 18 | } 19 | } 20 | } 21 | 22 | impl Native { 23 | /// Returns the representation of a native symbol. 24 | /// 25 | /// A call to this method always results in a static str. 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// # use paste_lang::Native; 30 | /// let nat = Native::Assign; 31 | /// 32 | /// assert_eq!(nat.to_str(), "="); 33 | /// ``` 34 | #[inline] #[must_use] 35 | pub fn to_str(&self) -> &'static str { 36 | match self { 37 | $(Native::$tok => $text,)+ 38 | } 39 | } 40 | } 41 | }; 42 | } 43 | 44 | impl_native_enum!( 45 | Assign => "=", 46 | Comma => ",", 47 | Do => "do", 48 | Defer => "defer", 49 | If => "if", 50 | While => "while", 51 | Tern => "?", 52 | Copy => "copy", 53 | Xch => "xch", 54 | Put => "put", 55 | Add => "+", 56 | Sub => "-", 57 | Mul => "*", 58 | Div => "/", 59 | Eq => "==", 60 | Less => "<", 61 | Floor => "floor", 62 | Exit => "exit", 63 | ); 64 | 65 | impl fmt::Display for Native { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | write!(f, "{}", self.to_str()) 68 | } 69 | } 70 | 71 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 72 | pub enum Sym { 73 | Native(Native), 74 | Int(i64), 75 | Float(R64), 76 | Text(Rc), 77 | Block(Vec), 78 | Defer(Box), 79 | } 80 | 81 | impl PartialOrd for Sym { 82 | fn partial_cmp(&self, rhs: &Self) -> Option { 83 | match (self, rhs) { 84 | (Sym::Int(x), Sym::Int(y)) => x.partial_cmp(y), 85 | (Sym::Int(x), Sym::Float(y)) => r64(*x as _).partial_cmp(y), 86 | (Sym::Float(x), Sym::Int(y)) => x.partial_cmp(&r64(*y as _)), 87 | (Sym::Float(x), Sym::Float(y)) => x.partial_cmp(y), 88 | (x, y) => x.to_string().as_str().partial_cmp(y.to_string().as_str()), 89 | } 90 | } 91 | } 92 | 93 | impl Sym { 94 | #[inline] 95 | pub fn text(s: impl Into>) -> Self { 96 | Self::Text(s.into()) 97 | } 98 | 99 | #[inline] 100 | pub fn concat(a: impl fmt::Display, b: impl fmt::Display) -> Self { 101 | Sym::text(format!("{}{}", a, b)) 102 | } 103 | 104 | #[inline] 105 | pub fn int(n: impl Into) -> Self { 106 | Sym::Int(n.into()) 107 | } 108 | 109 | #[inline] 110 | pub fn float(f: impl Into) -> Self { 111 | Sym::Float(r64(f.into())) 112 | } 113 | 114 | #[inline] 115 | fn interned_str(s: &str, cache: &mut HashSet>) -> Self { 116 | if let Ok(n) = Native::from_str(s) { 117 | Sym::Native(n) 118 | } else if let Some(cached) = cache.get(s) { 119 | Sym::Text(cached.clone()) 120 | } else { 121 | let s: Rc = s.into(); 122 | cache.insert(s.clone()); 123 | Sym::text(s) 124 | } 125 | } 126 | } 127 | 128 | impl fmt::Display for Sym { 129 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 130 | match self { 131 | Sym::Native(n) => write!(f, "{}", n.to_str()), 132 | Sym::Int(s) => write!(f, "{}", s), 133 | Sym::Float(s) => write!(f, "{}", s), 134 | Sym::Text(s) => { 135 | if s.contains(' ') { 136 | write!(f, "\"{}\"", s) 137 | } else { 138 | write!(f, "{}", s) 139 | } 140 | } 141 | Sym::Block(s) => { 142 | write!(f, "{{ ")?; 143 | for sym in s.iter() { 144 | write!(f, "{} ", sym)?; 145 | } 146 | write!(f, "}}") 147 | } 148 | Sym::Defer(s) => write!(f, ";{}", *s), 149 | } 150 | } 151 | } 152 | 153 | /// Checks if a token stream is complete and ready to parse. 154 | /// 155 | /// This function runs through the given token iterator and checks if every 156 | /// opening bracket has a closing counterpart. Inversely, if the function 157 | /// returns false, the token stream is not be complete and must be continued 158 | /// to be able to parse correctly. The function may also result in an error, 159 | /// if brackets are placed incorrectly. 160 | /// 161 | /// # Examples 162 | /// ``` 163 | /// # use paste_lang::*; 164 | /// let unfinished = check_complete(lex("t ;(put hello")); 165 | /// let complete = check_complete(lex("t ;(put hello) =")); 166 | /// let invalid = check_complete(lex("t ;{put hello) =")); 167 | /// 168 | /// assert_eq!(unfinished, Ok(false)); 169 | /// assert_eq!(complete, Ok(true)); 170 | /// assert!(invalid.is_err()); 171 | /// ``` 172 | /// 173 | /// # Errors 174 | /// If the given piece of code cannot be completed to valid paste code, 175 | /// a string describing the problem is returned. 176 | pub fn check_complete<'a>(tokens: impl Iterator>) -> Result { 177 | let mut stack = Vec::::new(); 178 | 179 | for token in tokens { 180 | match token { 181 | Token::LeftCurly | Token::LeftParen => { 182 | let curly = token == Token::LeftCurly; 183 | stack.push(curly) 184 | } 185 | Token::RightCurly | Token::RightParen => { 186 | let curly = token == Token::RightCurly; 187 | match stack.pop() { 188 | None => return Err("too many closing brackets"), 189 | Some(b) if b != curly => return Err("wrong closing brackets"), 190 | Some(_) => {} 191 | } 192 | } 193 | _ => {} 194 | } 195 | } 196 | 197 | Ok(stack.is_empty()) 198 | } 199 | 200 | /// Parses a given token stream. 201 | /// 202 | /// It converts a given token iterator into a vector of symbols, which is 203 | /// ready to be evaluated. 204 | /// 205 | /// # Errors 206 | /// If the given piece of code cannot be completed to valid paste code, 207 | /// a string describing the problem is returned. 208 | pub fn parse<'a>(tokens: impl Iterator>) -> Result, &'static str> { 209 | let mut stack = vec![(Vec::new(), 0_u32, false)]; 210 | let mut symbol_cache = HashSet::>::new(); 211 | 212 | for token in tokens { 213 | let (out, defer_level, _) = stack.last_mut().ok_or("too many closing brackets")?; 214 | 215 | match token { 216 | Token::Str(s) => { 217 | out.push(Sym::interned_str(s, &mut symbol_cache)); 218 | } 219 | 220 | Token::String(s) => { 221 | out.push(Sym::interned_str(s.as_str(), &mut symbol_cache)); 222 | } 223 | 224 | Token::Int(i) => { 225 | out.push(Sym::int(i)); 226 | } 227 | 228 | Token::Float(f) => { 229 | out.push(Sym::float(f)); 230 | } 231 | 232 | Token::SemiColon => { 233 | *defer_level += 1; 234 | continue; 235 | } 236 | 237 | Token::LeftCurly | Token::LeftParen => { 238 | let reverse = token == Token::LeftParen; 239 | stack.push((Vec::new(), 0, reverse)); 240 | continue; 241 | } 242 | 243 | Token::RightCurly | Token::RightParen => { 244 | if *defer_level > 0 { 245 | return Err("nothing to defer"); 246 | } 247 | 248 | // SAFETY: The case that the stack is empty is caught 249 | // in the beginning of the loop 250 | let (mut inner, _, reverse) = stack.pop().unwrap(); 251 | 252 | if reverse != (token == Token::RightParen) { 253 | return Err("wrong closing brackets"); 254 | } 255 | 256 | if reverse { 257 | inner.reverse(); 258 | } 259 | 260 | stack 261 | .last_mut() 262 | .ok_or("closing bracket without a friend")? 263 | .0 264 | .push(Sym::Block(inner)); 265 | } 266 | 267 | Token::Tick => { 268 | let sym = out.pop().ok_or("tick without a symbol")?; 269 | out.insert(0, sym); 270 | } 271 | } 272 | 273 | let (out, defer_level, _) = stack.last_mut().ok_or("too many closing brackets")?; 274 | 275 | if *defer_level > 0 { 276 | for _ in 0..*defer_level { 277 | // SAFETY: The unwrap is only reached after an sym has 278 | // been pushed, because the loop is continued otherwise 279 | let inner = Box::new(out.pop().unwrap()); 280 | out.push(Sym::Defer(inner)); 281 | } 282 | *defer_level = 0; 283 | } 284 | } 285 | 286 | let (prog, trailing_defer, _) = stack.pop().ok_or("opening bracket without a friend")?; 287 | 288 | if trailing_defer > 0 { 289 | return Err("trailing defer"); 290 | } 291 | 292 | if !stack.is_empty() { 293 | return Err("too many opening brackets"); 294 | } 295 | 296 | Ok(prog) 297 | } 298 | 299 | #[cfg(test)] 300 | mod test { 301 | use super::*; 302 | 303 | #[test] 304 | fn check_complete_true() { 305 | let lexer = lex("test ;{ (;{ put } hello) do } ="); 306 | let result = check_complete(lexer); 307 | assert_eq!(result, Ok(true)); 308 | } 309 | 310 | #[test] 311 | fn check_complete_false() { 312 | let lexer = lex("test ;{ (;{ put } hello"); 313 | let result = check_complete(lexer); 314 | assert_eq!(result, Ok(false)); 315 | } 316 | 317 | #[test] 318 | fn check_complete_too_many_closing() { 319 | let lexer = lex("{{b} {a}} c}"); 320 | assert!(parse(lexer).is_err()); 321 | } 322 | 323 | #[test] 324 | fn check_complete_bracket_mismatch() { 325 | let lexer = lex("( ;a put }"); 326 | assert!(parse(lexer).is_err()); 327 | } 328 | 329 | #[test] 330 | fn check_complete_sub_zero() { 331 | let lexer = lex("{ab}} {{cd}"); 332 | assert!(parse(lexer).is_err()); 333 | } 334 | 335 | #[test] 336 | fn parse_simple() { 337 | let lexer = lex("test { \"hello\" { ;put } do }"); 338 | let prog = parse(lexer).unwrap(); 339 | let result = format!("{:?}", prog); 340 | assert_eq!( 341 | result, 342 | "[Text(\"test\"), \ 343 | Block([Text(\"hello\"), Block([Defer(Native(Put))]), \ 344 | Native(Do)])]" 345 | ); 346 | } 347 | 348 | #[test] 349 | fn parse_deferred_blocks() { 350 | let lexer = lex("1 ;{ ;{ hello } do ;put do } do"); 351 | let prog = parse(lexer).unwrap(); 352 | let result = format!("{:?}", prog); 353 | assert_eq!( 354 | result, 355 | "[Int(1), \ 356 | Defer(Block([Defer(Block([Text(\"hello\")])), Native(Do), \ 357 | Defer(Native(Put)), Native(Do)])), Native(Do)]" 358 | ); 359 | } 360 | 361 | #[test] 362 | fn parse_tick_simple() { 363 | let lexer = lex("3 4' (2 +' 3)'' * +"); 364 | let prog = parse(lexer).unwrap(); 365 | let result = format!("{:?}", prog); 366 | assert_eq!( 367 | result, 368 | "[Int(3), Block([Int(3), Int(2), \ 369 | Native(Add)]), Int(4), Native(Mul), Native(Add)]" 370 | ); 371 | } 372 | 373 | #[test] 374 | fn parse_paren_simple() { 375 | let lexer = lex("(1 +' (2 *' 3)) put"); 376 | let prog = parse(lexer).unwrap(); 377 | let result = format!("{:?}", prog); 378 | assert_eq!( 379 | result, 380 | "[Block([Block([Int(3), Int(2), \ 381 | Native(Mul)]), Int(1), Native(Add)]), Native(Put)]" 382 | ); 383 | } 384 | 385 | #[test] 386 | fn parse_block_trailing_defer() { 387 | let lexer = lex("a {;b ;}"); 388 | assert!(parse(lexer).is_err()); 389 | } 390 | 391 | #[test] 392 | fn parse_trailing_defer() { 393 | let lexer = lex(";a ;{b} ;"); 394 | assert!(parse(lexer).is_err()); 395 | } 396 | 397 | #[test] 398 | fn parse_brackets_not_closing() { 399 | let lexer = lex("{b} {a{c}"); 400 | assert!(parse(lexer).is_err()); 401 | } 402 | 403 | #[test] 404 | fn parse_brackets_too_many_closing() { 405 | let lexer = lex("{{b} {a}} c}"); 406 | assert!(parse(lexer).is_err()); 407 | } 408 | 409 | #[test] 410 | fn parse_brackets_bracket_mismatch() { 411 | let lexer = lex("( ;a put }"); 412 | assert!(parse(lexer).is_err()); 413 | } 414 | 415 | #[test] 416 | fn parse_brackets_sub_zero() { 417 | let lexer = lex("{ab}} {{cd}"); 418 | assert!(parse(lexer).is_err()); 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /src/std.paste: -------------------------------------------------------------------------------- 1 | # paste - standard library 2 | 3 | # Stack manipulation: 4 | ;{ 1 copy } dup = 5 | ;{ dup ;_n = 1 < ;{ dup 1 _n - dup ;_n = } while pop } rep = 6 | ;{ _ = } pop = 7 | # ;{ defer ;_x = defer ;_y = _x _y } xch = 8 | ;{ 2 copy pop } over = 9 | ;{ defer ;_x = _ = _x } under = 10 | 11 | # Unary operators: 12 | ;{ 1 + } ++ = 13 | ;{ 1 xch - } -- = 14 | ;{ dup * } sq = 15 | ;{ 0 - } neg = 16 | ;{ 1 / } inv = 17 | 18 | # Comparison operators: 19 | ;{ xch < } > = 20 | ;{ == ! } != = 21 | ;{ > ! } <= = 22 | ;{ < ! } >= = 23 | 24 | # Boolean operators: 25 | ;{ ;0 ;1 ? } ! = 26 | ;{ ;do ;{ pop 0 } ? } && = 27 | ;{ ;{ pop 1 } ;do ? } || = 28 | 29 | # Compound operators 30 | ;{ ;_op = dup do _op do xch = } map = 31 | ;{ dup defer ;_r = do + _r = } += = 32 | ;{ dup defer ;_r = do - _r = } -= = 33 | ;{ dup defer ;_r = do * _r = } *= = 34 | ;{ dup defer ;_r = do / _r = } /= = 35 | 36 | # Basic math: 37 | ;{ / floor } div = 38 | ;{ xch 2 copy xch / floor * xch - } mod = 39 | ;{ 2 copy < ;xch if pop } min = 40 | ;{ 2 copy > ;xch if pop } max = 41 | ;{ dup 0 - max } abs = 42 | ;{ 0.5 + floor } round = 43 | ;{ neg floor neg } ceil = 44 | ;{ dup floor xch - } fract = 45 | 46 | # Control flow: 47 | ;{ 0 exit } quit = 48 | ;{ -1 exit } abort = 49 | # ;{ 1 xch ;{} ? } do = 50 | # ;{ ;{} ? } if = 51 | # ;{ defer dup do , ;while xch + ;{} ? } while = 52 | --------------------------------------------------------------------------------