1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//! The Teko Programming Language implemented in Rust. //! //! # About # //! This implementation provides parsing and evaluation utilities of the Teko programming language. //! Teko belongs to the family of **Lisp-1** languages and features **dynamic scoping**, //! **first-class macros**, and **tail call optimization**. //! //! Teko is made to be used as an everyday scripting language. The language was designed to be //! easy to implement yet useful. Comparing Teko to other Lisps reveals the core motivation: //! to implement a super-minimal Lisp capable of being a fully fledged programming language. //! //! Teko is has the property that it's **strictly evaluated** yet lacks **interior mutability**. //! This allows //! the implementation to opt for **reference counted** garbage collection - because cycles can't //! be created - which is desirable in real-time applications as it doesn't cause unforeseen //! pauses in execution. //! //! # Why Lisp? # //! Here's my favorite excerpt that words it perfectly, from //! [Let Over Lambda](https://letoverlambda.com/index.cl/guest/chap1.html#sec_1), ch. 1: //! //! ```text //! Macros are the single greatest advantage that lisp has as a programming language and the single //! greatest advantage of any programming language. With them you can do things that you simply //! cannot do in other languages. Because macros can be used to transform lisp into other //! programming languages and back, programmers who gain experience with them discover that all //! other languages are just skins on top of lisp. This is the big deal. Lisp is special because //! programming with it is actually programing at a higher level. Where most languages invent and //! enforce syntactic and semantic rules, lisp is general and malleable. //! With lisp, you make the rules. //! ``` //! //! # Examples Code # //! Here is the iconic `hello world` in Teko: //! //! ```text //! (" Hello world!) //! ``` //! No functional-like language is complete without the definition of the tail-recursive factorial //! function. //! //! ```text //! (def factorial (fn (n accum) //! (if (= n 1) //! accum //! (factorial (- n 1) (* n accum))))) //! (factorial 5 1) //! ``` //! # Usage # //! Example: using this library to interpret Teko: //! //! ``` //! extern crate teko; //! extern crate num_traits; //! use num_traits::cast::ToPrimitive; //! fn main() { //! let program = teko::parse::parse_string(" //! (def factorial (fn (n accum) //! (if (= n 1) //! accum //! (factorial (- n 1) (* n accum))))) //! (write (factorial 5 1))").ok().unwrap(); //! let env = teko::interpret::interpret(program); //! //! match env.result.1 { //! teko::data_structures::Coredata::Integer(ref value) => { //! assert_eq![value.to_i32().unwrap(), 120]; //! } //! _ => { //! panic!["Expected Integer but got a different data type"]; //! } //! } //! } //! ``` //! //! Note that `write` doesn't yield a result in the example above so the previous //! result from `factorial` is left inside the environment instead. // ////////////////////////////////////////////////////////// // ✓ Implementor's checklist: (✓ = Implemented | ✗ = rejected | empty = unimplemented // // ✓ Core expansion, parameterizations, and preparation // ✓ Builtin Function calls // ✓ Builtin Macro calls // ✓ Tail call optimization // ✓ If branching // ✓ Integer parsing // ✓ head/tail/pair // ✓ wind/unwind // ✓ ' quote // ✓ " strings // ✓ Add the error creation function // ✓ Make Source data optional // ✓ Macroize the initial environment (to clean up code) // ✓ Replace panics with unwinds in 'fn eval' // ✓ Change transfer functions, do we need top? // ✓ Use booleans for If // ✓ Easily add constants (pi, e, true, false) // ✓ Sort the builtins.rs file by function names // ✓ Improve error unwinding (do we need to pop params?), add formal errors // ✓ transfer -> Option<String> for consistent error handling // ✗ ` quasiquote - Can be built from primitives // ✗ Test different TCO strategies (HashSet, sorted Vec,..) - Not important // ✗ Implement powers for numbers - Implemented using primitives // ✗ Rational parsing + promotion - Not minimal // ✗ Complex parsing + promotion - Not minimal // ✓ <, >, =, <=, >=, != number comparison - Only < and == builtin, others derived // ✓ Boolean not, and, or // ✗ Create a builtin error registry - Not minimal, keep errors short // ✓ quote ✓ symbol? ✓ same? ✓ pair? ✓ head ✓ tail ✓ pair ✓ if ✓ fn ✓ mo // ✗ Create FFI for C - Not minimal // ✗ Functional map/set/trie/fingertree - Not very minimal // ✗ Multithreading - Not part of the idealized language // ✗ Channels - As above // ✗ Make Userdata easily editable - Is only a reference impl, no need // ✓ Replace all panics with unwinds // ✓ Sort imports and uses where possible // ✓ Implement a proper fmt::Display for Sourcedata // ✓ Actually make error handling consistent + stacktrace // ✓ Clippify everything // ✓ Write tests (not complete, but the structure is there) // ✓ Document everything // ✓ Make readme and build instructions // ✓ Escape strings during printing // ✓ Add builtin doc to retrieve documentation about functions // Pretty printing // Colorize errors // Create extension interface (not sure if feasible atm) // Reconsider access to program stack // Split project // Flesh out specification // // ////////////////////////////////////////////////////////// #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] extern crate num; #[macro_use] mod macros; pub mod builtins; pub mod data_structures; pub mod interpret; pub mod parse; pub mod utilities; // Preallocate buffers for each Vec const VEC_CAPACITY: usize = 100;