├── LICENSE ├── README.md ├── build.sbt ├── builtins.lisp ├── project └── plugins.sbt ├── src ├── main │ └── scala │ │ ├── builtins.scala │ │ ├── compiled_builtins.scala │ │ ├── compiler.scala │ │ ├── completer.scala │ │ ├── interpreter.scala │ │ ├── parser.scala │ │ ├── preprocessor.scala │ │ └── scalisp.scala └── test │ └── scala │ ├── BugFixes.scala │ ├── Compiler.scala │ ├── Constants.scala │ ├── If.scala │ ├── Interpretion.scala │ ├── Lambda.scala │ ├── Parsing.scala │ ├── Procedures.scala │ ├── SampleCode.scala │ └── Variables.scala └── test.lisp /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Julian Schrittwieser 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 | .oooooo..o oooo o8o 2 | d8P' `Y8 `888 `"' 3 | Y88bo. .ooooo. .oooo. 888 oooo .oooo.o oo.ooooo. 4 | `"Y8888o. d88' `"Y8 `P )88b 888 `888 d88( "8 888' `88b 5 | `"Y88b 888 .oP"888 888 888 `"Y88b. 888 888 6 | oo .d8P 888 .o8 d8( 888 888 888 o. )88b 888 888 7 | 8""88888P' `Y8bod8P' `Y888""8o o888o o888o 8""888P' 888bod8P' 8 | 888 9 | o888o 10 | 11 | A lisp interpreter and compiler written in Scala, inspired by [Peter Norvig](http://norvig.com/lispy.html) 12 | 13 | Download 14 | ======== 15 | 16 | You can either clone this repo and use `sbt` or you can download the precompiled 17 | `jar` and execute that. 18 | 19 | To be able to compile the resulting scala code, you should also download 20 | `compiled_builtins.scala` and place it in the same directory as the compiled 21 | `.scala` file. 22 | 23 | Interpeter 24 | ========== 25 | 26 | It's a bit incomplete and probably buggy, but it works good enough to for most applications: 27 | 28 | ```lisp 29 | (define fact (lambda (n) (if (< n 2) 1 (* n (fact (- n 1)))))) 30 | (fact 10) 31 | ``` 32 | 33 | produces `3628800` 34 | 35 | Also, Merge sort can be implemented without a problem: 36 | 37 | ```lisp 38 | ; note that you can also use comments 39 | ; and split functions over multiple lines for readability 40 | (define msort 41 | (lambda (list) 42 | (if (<= (length list) 1) 43 | list 44 | (begin 45 | (define split (/ (length list) 2)) 46 | (merge 47 | (msort (subseq list 0 split)) 48 | (msort (subseq list split)) 49 | ) 50 | ) 51 | ) 52 | ) 53 | ) 54 | 55 | ; ordering is not important, as functions are evaluated lazily 56 | (define merge 57 | (lambda (a b) 58 | (if (< (length a) 1) 59 | b 60 | (if (< (length b) 1) 61 | a 62 | (if (< (car a) (car b)) 63 | (cons (car a) (merge (cdr a) b)) 64 | (cons (car b) (merge a (cdr b))) 65 | ) 66 | ) 67 | ) 68 | ) 69 | ) 70 | ``` 71 | 72 | Usage is just like you'd expect 73 | 74 | ```lisp 75 | (msort '(5 7 2 1 3 4 6)) 76 | ``` 77 | 78 | And results in `'(1 2 3 4 5 6 7)` 79 | 80 | It's also possible to execute files, simply do 81 | 82 | scalisp filename.l 83 | 84 | or, from the sbt-console 85 | 86 | run filename.l 87 | 88 | Compiler 89 | ======== 90 | 91 | Instead of taking the traditional route and compiling to byte code (which 92 | Clojure already does), I decided to compile to Scala instead. Just use `scalisp` 93 | like you would for interpetation, but add the `-c` switch. 94 | 95 | For example, the compiled merge sort looks like this: 96 | 97 | ```scala 98 | def merge(a: Any, b: Any): Any = { 99 | if(length(a) < 1l) { 100 | b 101 | } else { 102 | if(length(b) < 1l) { 103 | a 104 | } else { 105 | if(car(a) < car(b)) { 106 | car(a) :: merge(cdr(a), b) 107 | } else { 108 | car(b) :: merge(a, cdr(b)) 109 | } 110 | } 111 | } 112 | } 113 | 114 | def msort(list: Any): Any = { 115 | if(length(list) <= 1l) { 116 | list 117 | } else { 118 | { 119 | var split = (length(list) / 2l) 120 | merge(msort(subseq(list, 0l, split)), msort(subseq(list, split))) 121 | } 122 | } 123 | } 124 | ``` 125 | 126 | If you specify a whole file, you'll get a complete `.scala` file back, ready to 127 | be compiled. If you just execute `scalisp -c`, you get a REPL, which simply 128 | compiles snippets of code. -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | name := "Scalisp" 3 | 4 | version := "1.1.1" 5 | 6 | scalaVersion := "2.11.6" 7 | 8 | libraryDependencies ++= Seq( 9 | "org.scalatest" %% "scalatest" % "2.2.4" % "test", 10 | "org.scala-lang.modules" % "scala-parser-combinators_2.11" % "1.0.4", 11 | ("org.scala-lang" % "jline" % "2.10.6") 12 | .exclude("org.fusesource.jansi", "jansi") 13 | .exclude("jline", "jline"), 14 | "org.clapper" %% "argot" % "1.0.4" 15 | ) 16 | 17 | 18 | assemblyMergeStrategy in assembly := { 19 | case n if n.endsWith("linux32/libjansi.so") => MergeStrategy.discard 20 | case n if n.endsWith("linux64/libjansi.so") => MergeStrategy.discard 21 | case n if n.endsWith("osx/libjansi.jnilib") => MergeStrategy.discard 22 | case n if n.endsWith("jansi.dll") => MergeStrategy.discard 23 | case n if n.startsWith("org/fusesource/jansi/") => MergeStrategy.last 24 | case n if n.startsWith("org/fusesource/hawtjni/runtime/") => MergeStrategy.last 25 | case x => 26 | val oldStrategy = (assemblyMergeStrategy in assembly).value 27 | oldStrategy(x) 28 | } 29 | -------------------------------------------------------------------------------- /builtins.lisp: -------------------------------------------------------------------------------- 1 | (defun __length (list acc) 2 | (if (= list '()) 3 | acc 4 | (__length (cdr list) (+ 1 acc)) 5 | ) 6 | ) 7 | 8 | (defun length (list) 9 | (__length list 0) 10 | ) 11 | 12 | 13 | 14 | (defun __reverse (l acc) 15 | (if (= '() l) 16 | acc 17 | (__reverse (cdr l) (cons (car l) acc)) 18 | ) 19 | ) 20 | 21 | (defun reverse (l) 22 | (__reverse l '()) 23 | ) 24 | 25 | 26 | 27 | (defun __subseq (list start stop acc) 28 | (if (> start 0) 29 | (__subseq (subseq list start) 0 (- stop start) acc) 30 | (if (<= stop 0) 31 | (reverse acc) 32 | (__subseq (cdr list) 0 (- stop 1) (cons (car list) acc)) 33 | ) 34 | ) 35 | ) 36 | 37 | (defun subseq (list start) 38 | (if (<= start 0) 39 | list 40 | (subseq (cdr list) (- start 1)) 41 | ) 42 | ) 43 | 44 | (defun subseq (list start stop) 45 | (__subseq list start stop '()) 46 | ) 47 | 48 | 49 | 50 | (defun __range (start stop acc) 51 | (if (<= stop start) 52 | (reverse acc) 53 | (__range (+ start 1) stop (cons start acc)) 54 | ) 55 | ) 56 | 57 | (defun range (stop) 58 | (__range 0 stop '()) 59 | ) 60 | 61 | (defun range (start stop) 62 | (__range start stop '()) 63 | ) 64 | 65 | 66 | (defun map (f seq) 67 | (foldr (lambda (elem acc) (cons (f elem) acc) ) '() seq) 68 | ) 69 | 70 | 71 | (defun filter (f seq) 72 | (foldr (lambda (elem acc) (if (f elem) (cons elem acc) acc)) '() seq) 73 | ) 74 | 75 | 76 | (defun foldl (f acc seq) 77 | (if (= '() seq) 78 | acc 79 | (foldl f (f acc (car seq)) (cdr seq)) 80 | ) 81 | ) 82 | 83 | (defun foldr (f acc seq) 84 | (if (= '() seq) 85 | acc 86 | (foldr f (f (last seq) acc) (init seq)) 87 | ) 88 | ) 89 | 90 | (defun reduce (f seq) 91 | (foldl f (car seq) (cdr seq)) 92 | ) 93 | 94 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns) 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0") 4 | -------------------------------------------------------------------------------- /src/main/scala/builtins.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | class MySeq[T](l: Seq[T]) { 4 | def toDouble = try { 5 | l.map { 6 | case l: Long => l.toDouble 7 | case d: Double => d 8 | } 9 | } catch { 10 | case e: MatchError => throw new TypeError("couldn't convert to double: " + e) 11 | } 12 | 13 | def toLong = try { 14 | l.map { 15 | case l: Long => l 16 | case d: Double => d.toLong 17 | } 18 | } catch { 19 | case e: MatchError => throw new TypeError("couldn't convert to long: " + e) 20 | } 21 | 22 | def allLong = l.forall { 23 | case l: Long => true 24 | case _ => false 25 | } 26 | 27 | def eval(env: Env) = l.map(e => Interpreter.eval(e, env)) 28 | } 29 | 30 | object Helper { 31 | implicit def Seq2MySeq[T](l: Seq[T]) = new MySeq[T](l) 32 | } 33 | 34 | import Helper._ 35 | 36 | object Builtins { 37 | def compare( 38 | op: (Double, Double) => Boolean, 39 | initOffset: Double, 40 | n: Seq[Any], 41 | env: Env 42 | ): Boolean = { 43 | val xs = n.eval(env).toDouble 44 | xs.fold( (xs(0) + initOffset, true) ) { 45 | case ( (prev: Double, valid: Boolean), cur: Double) => 46 | if(valid && op(prev, cur)) (cur, true) else (cur, false) 47 | } match { case (_, flag: Boolean) => flag } 48 | } 49 | 50 | def opD(l: List[Any], f: (Double, Double) => Double, env: Env) = { 51 | l.eval(env).toDouble.reduce(f) 52 | } 53 | 54 | def opL(l: List[Any], f: (Long, Long) => Long, env: Env) = { 55 | l.eval(env).toLong.reduce(f) 56 | } 57 | 58 | def argCount(l: List[Any], n: Int) { 59 | if(l.length - 1 != n) throw new InterpreterException( 60 | "invalid number of arguments: expected %d, got %d".format(n, l.length - 1)) 61 | } 62 | 63 | val ops = List("+", "-", "*", "/", "%", "min", "max") 64 | 65 | def builtins(l: List[Any], env: Env): PartialFunction[String, Any] = { 66 | // arithmetic 67 | case op: String if ops.contains(op) => 68 | val args = l.tail.eval(env) 69 | op match { 70 | case "+" => if(args.allLong) args.toLong.reduce(_ + _) else args.toDouble.reduce(_ + _) 71 | case "-" => if(args.allLong) args.toLong.reduce(_ - _) else args.toDouble.reduce(_ - _) 72 | case "*" => if(args.allLong) args.toLong.reduce(_ * _) else args.toDouble.reduce(_ * _) 73 | case "/" => if(args.allLong) args.toLong.reduce(_ / _) else args.toDouble.reduce(_ / _) 74 | case "min" => if(args.allLong) args.toLong.min else args.toDouble.min 75 | case "max" => if(args.allLong) args.toLong.max else args.toDouble.max 76 | } 77 | 78 | // comparisons 79 | case "<" => compare(_ < _, -1, l.tail, env) 80 | case ">" => compare(_ > _, 1, l.tail, env) 81 | case ">=" => compare(_ >= _, 0, l.tail, env) 82 | case "<=" => compare(_ <= _, 0, l.tail, env) 83 | case "=" => l.tail.eval(env).distinct.length == 1 84 | case "!=" => l.tail.eval(env).distinct.length > 1 85 | 86 | case "atom" => argCount(l, 1); Interpreter.eval(l(1), env) match { 87 | case l: List[Any] => false 88 | case _ => true 89 | } 90 | 91 | // string furnctions 92 | case "to-string" => argCount(l, 1); Interpreter.eval(l(1), env).toString 93 | case "concat" => l.tail.eval(env).mkString 94 | 95 | // list functions 96 | case "car" => argCount(l, 1); Interpreter.eval(l(1), env) match { 97 | case l: List[Any] => l.head 98 | case s: String => s.head 99 | case Literal(s) => s.head 100 | case _ => throw new TypeError("can't get head of non-list") 101 | } 102 | 103 | case "cdr" => argCount(l, 1); Interpreter.eval(l(1), env) match { 104 | case l: List[Any] => l.tail 105 | case s: String => s.tail 106 | case Literal(s) => s.tail 107 | case _ => throw new TypeError("can't get tail of non-list") 108 | } 109 | 110 | case "last" => argCount(l, 1); Interpreter.eval(l(1), env) match { 111 | case l: List[Any] => l.last 112 | case s: String => s.last 113 | case Literal(s) => s.last 114 | case _ => throw new TypeError("can't get tail of non-list") 115 | } 116 | 117 | case "init" => argCount(l, 1); Interpreter.eval(l(1), env) match { 118 | case l: List[Any] => l.init 119 | case s: String => s.init 120 | case Literal(s) => s.init 121 | case _ => throw new TypeError("can't get tail of non-list") 122 | } 123 | 124 | case "cons" => argCount(l, 2); Interpreter.eval(l(2), env) match { 125 | case list: List[Any] => Interpreter.eval(l(1), env) :: list 126 | case _ => throw new TypeError("can't cons to non-list") 127 | } 128 | 129 | case "append" => l.tail.eval(env).flatMap { 130 | case l: List[Any] => l 131 | case _ => throw new TypeError("can't append non-lists") 132 | } 133 | 134 | case "list" => l.tail.eval(env) 135 | 136 | case "shuffle" => argCount(l, 1); Interpreter.eval(l(1), env) match { 137 | case list: List[Any] => util.Random.shuffle(list) 138 | case _ => throw new TypeError("can't shuffle a non-list") 139 | } 140 | 141 | case "print" => println(l.tail.eval(env).mkString) 142 | 143 | case "dump-env" => println(env) 144 | } 145 | } -------------------------------------------------------------------------------- /src/main/scala/compiled_builtins.scala: -------------------------------------------------------------------------------- 1 | package CompiledApp 2 | 3 | import scala.annotation.tailrec 4 | 5 | class TypeError(s: String) extends Exception(s) { } 6 | 7 | class MySeq[T](l: Seq[T]) { 8 | def toDouble = try { 9 | l.map { 10 | case l: Long => l.toDouble 11 | case d: Double => d 12 | } 13 | } catch { 14 | case e: MatchError => throw new TypeError("couldn't convert to double: " + e) 15 | } 16 | 17 | def toLong = try { 18 | l.map { 19 | case l: Long => l 20 | case d: Double => d.toLong 21 | } 22 | } catch { 23 | case e: MatchError => throw new TypeError("couldn't convert to long: " + e) 24 | } 25 | 26 | def allLong = l.forall { 27 | case l: Long => true 28 | case _ => false 29 | } 30 | } 31 | 32 | import Helper._ 33 | object Helper { 34 | implicit def Seq2MySeq[T](l: Seq[T]) = new MySeq[T](l) 35 | implicit def Any2MyAny(a: Any) = new MyAny(a) 36 | implicit def Any2Boolean(a: Any) = a match { 37 | case b: Boolean => b 38 | case _ => throw new TypeError("Expected boolean") 39 | } 40 | } 41 | 42 | class MyAny(a: Any) { 43 | import CompiledBuiltins._ 44 | def <=(that: Any) = compare(_ <= _, -1, List(a, that)) 45 | def >(that: Any) = compare(_ > _, -1, List(a, that)) 46 | def <(that: Any) = compare(_ < _, -1, List(a, that)) 47 | def -(that: Any): Any = { 48 | val l = List(a, that) 49 | if(l.allLong) opL(l, _ - _) else opD(l, _ - _) 50 | } 51 | def +(that: Any): Any = { 52 | val l = List(a, that) 53 | if(l.allLong) opL(l, _ + _) else opD(l, _ + _) 54 | } 55 | def /(that: Any): Any = { 56 | val l = List(a, that) 57 | if(l.allLong) opL(l, _ / _) else opD(l, _ / _) 58 | } 59 | def *(that: Any): Any = { 60 | val l = List(a, that) 61 | if(l.allLong) opL(l, _ * _) else opD(l, _ * _) 62 | } 63 | def ::(that: Any): List[Any] = a match { 64 | case l: List[Any] => that :: l 65 | case _ => throw new TypeError("can only cons to list: %s, %s".format(a, that)) 66 | } 67 | def ++(that: Any) = a match { 68 | case l: List[Any] => that match { 69 | case m: List[Any] => l ++ m 70 | case _ => throw new TypeError("can only append lists") 71 | } 72 | case _ => throw new TypeError("can only append lists") 73 | } 74 | 75 | } 76 | 77 | case class Literal(s: String) 78 | 79 | object CompiledBuiltins { 80 | // builtin functions 81 | def compare( 82 | op: (Double, Double) => Boolean, 83 | initOffset: Double, 84 | n: Seq[Any] 85 | ): Boolean = { 86 | val xs = n.toDouble 87 | xs.fold( (xs(0) + initOffset, true) ) { 88 | case ( (prev: Double, valid: Boolean), cur: Double) => 89 | if(valid && op(prev, cur)) (cur, true) else (cur, false) 90 | } match { case (_, flag: Boolean) => flag } 91 | } 92 | 93 | def opD(l: List[Any], f: (Double, Double) => Double) = { 94 | l.toDouble.reduce(f) 95 | } 96 | 97 | def opL(l: List[Any], f: (Long, Long) => Long) = { 98 | l.toLong.reduce(f) 99 | } 100 | 101 | def print(args: Any*) = println(args.mkString) 102 | def <(args: Any*) = compare(_ < _, -1, args) 103 | def <=(args: Any*) = compare(_ <= _, -1, args) 104 | def car(l: Any) = l match { 105 | case list: List[Any] => list.head 106 | case _ => throw new TypeError("Expected list") 107 | } 108 | def cdr(l: Any) = l match { 109 | case list: List[Any] => list.tail 110 | case _ => throw new TypeError("Expected list") 111 | } 112 | def last(l: Any) = l match { 113 | case list: List[Any] => list.last 114 | case _ => throw new TypeError("Expected list") 115 | } 116 | def init(l: Any) = l match { 117 | case list: List[Any] => list.init 118 | case _ => throw new TypeError("Expected list") 119 | } 120 | def cons(x: Any, xs: Any) = xs match { 121 | case list: List[Any] => x :: list 122 | case _ => throw new TypeError("Expected list") 123 | } 124 | def list(args: Any*) = args 125 | def shuffle(l: Any) = l match { 126 | case list: List[Any] => util.Random.shuffle(list) 127 | case _ => throw new TypeError("Expected list") 128 | } 129 | 130 | val unit = List() 131 | 132 | // user methods 133 | def filter(f: (Any) => Any, seq: Any): Any = { 134 | if(List() == seq) { 135 | List() 136 | } else { 137 | if(f(car(seq))) { 138 | car(seq) :: filter(f, cdr(seq)) 139 | } else { 140 | filter(f, cdr(seq)) 141 | } 142 | } 143 | } 144 | 145 | def reduce(f: (Any, Any) => Any, seq: Any): Any = { 146 | foldl(f, car(seq), cdr(seq)) 147 | } 148 | 149 | def foldl(f: (Any, Any) => Any, acc: Any, seq: Any): Any = { 150 | if(List() == seq) { 151 | acc 152 | } else { 153 | foldl(f, f(acc, car(seq)), cdr(seq)) 154 | } 155 | } 156 | 157 | def map(f: (Any) => Any, seq: Any): Any = { 158 | if(List() == seq) { 159 | List() 160 | } else { 161 | f(car(seq)) :: map(f, cdr(seq)) 162 | } 163 | } 164 | 165 | @tailrec def rec_range(start: Any, stop: Any, acc: Any): Any = { 166 | if(stop <= start) { 167 | reverse(acc) 168 | } else { 169 | rec_range(start + 1l, stop, start :: acc) 170 | } 171 | } 172 | 173 | def range(start: Any, stop: Any): Any = { 174 | rec_range(start, stop, List()) 175 | } 176 | 177 | def range(stop: Any): Any = { 178 | range(0l, stop) 179 | } 180 | 181 | def subseq(list: Any, start: Any, stop: Any): Any = { 182 | if(start > 0l) { 183 | subseq(subseq(list, start), 0l, stop - start) 184 | } else { 185 | if(stop <= 0l) { 186 | List() 187 | } else { 188 | car(list) :: subseq(cdr(list), 0l, stop - 1l) 189 | } 190 | } 191 | } 192 | 193 | def subseq(list: Any, start: Any): Any = { 194 | if(start <= 0l) { 195 | list 196 | } else { 197 | subseq(cdr(list), start - 1l) 198 | } 199 | } 200 | 201 | @tailrec def reverse(l: Any, acc: Any = List()): Any = { 202 | if(List() == l) { 203 | acc 204 | } else { 205 | reverse(cdr(l), car(l) :: acc) 206 | } 207 | } 208 | 209 | @tailrec def length(list: Any, acc: Any = 0l): Any = { 210 | if(list == List()) { 211 | acc 212 | } else { 213 | length(cdr(list), 1l + acc) 214 | } 215 | } 216 | 217 | 218 | } -------------------------------------------------------------------------------- /src/main/scala/compiler.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | class CompilerError(s: String) extends Exception(s) { } 4 | 5 | object ScalispCompiler { 6 | var global_functions = List[String]() 7 | var higher_order_funs = collection.mutable.Map[String, 8 | collection.mutable.Map[Int, Int]]().withDefault(_ => collection.mutable.Map[Int, Int]()) 9 | 10 | 11 | def compile(src: String, filename: String) = { 12 | // strip comments and replace newlines with spaces 13 | val ast = LispParser.parse(src.replaceAll(";[^\n$]*", " ").replace("\n", " ")) 14 | 15 | val code = Preprocessor.process(ast).map(e => process(e)).filter(_.length > 0).mkString("\n\n ") 16 | 17 | (""" 18 | object %s extends App { 19 | import CompiledApp.CompiledBuiltins._ 20 | import CompiledApp.Helper._ 21 | 22 | // user methods 23 | %s 24 | 25 | // main code 26 | %s 27 | } 28 | """.format(filename.split("\\.").head, global_functions.mkString("\n\n "), code)) 29 | } 30 | 31 | def compileLine(src: String) = { 32 | global_functions = List() 33 | val ast = LispParser.parse(src.replaceAll(";[^\n$]*", " ").replace("\n", " ")) 34 | val code = Preprocessor.process(ast).map(e => process(e)).filter(_.length > 0).mkString("\n\n ") 35 | global_functions.mkString("\n\n ") + code 36 | } 37 | 38 | def isFunction(name: String, body: Any): Int = body match { 39 | case n :: tail if n == name => tail.length 40 | case Nil => -1 41 | case l: List[Any] => l.map(e => isFunction(name, e)).max 42 | case _ => -1 43 | } 44 | 45 | case class UsedIn(name: String, argPos: Int) 46 | def usedInFunction(name: String, exp: Any): Option[UsedIn] = exp match { 47 | case Nil => None 48 | case l: List[Any] => 49 | if(l.tail.contains(name)) 50 | l.head match { 51 | case s: String => Some( UsedIn(s, l.indexOf(name) - 1) ) 52 | case _ => None 53 | } 54 | 55 | else 56 | l.tail.map(e => usedInFunction(name, e)).flatten.headOption 57 | case _ => None 58 | } 59 | 60 | 61 | def process(exp: Any, indent: String = " "): String = exp match { 62 | case l: List[Any] => l.head match { 63 | // special forms 64 | case "if" => ("if(%s) {\n" + indent + " %s\n" + indent +"} else {\n" + indent + " %s\n" + indent + "}").format( process(l(1)), process(l(2), indent + " "), process(l(3), indent + " ") ) 65 | case "define" => l(1) match { 66 | case name: String => "var %s = %s".format(name, process(l(2))) 67 | case _ => throw new CompilerError("variable name has to be a string") 68 | } 69 | case "set!" => l(1) match { 70 | case name: String => "%s = %s".format(name, process(l(2))) 71 | case _ => throw new CompilerError("variable name has to be a string") 72 | } 73 | case "begin" => l.tail.map(e => process(e, indent + " ")).mkString("{\n " + indent, "\n " + indent, "\n" + indent + "}") 74 | case "quote" => stringify(l(1)) 75 | case "lambda" => l(1) match { 76 | case parms: List[Any] => 77 | val p = parms.map { 78 | case n: String => n 79 | case _ => throw new CompilerError("parm names have to be strings") 80 | } 81 | val args = p.map(_ + ": Any").mkString(", ") 82 | val body = process(l(2), indent + " ") 83 | if(body.length < 20) 84 | "(%s) => { %s }".format(args, body) 85 | else 86 | ("(%s) => {\n" + indent + " %s\n" + indent + "}").format(args, body) 87 | case _ => throw new CompilerError("lambda arguments have to be a list") 88 | } 89 | case "let" => l(1) match { 90 | case names: List[String] => l(2) match { 91 | case vals: List[Any] => 92 | val body = l(3) 93 | 94 | val prelude = names.zip(vals).map { 95 | case (param: String, value: Any) => "val %s = %s".format(param, process(value, indent + " ")) 96 | } 97 | ("{\n" + indent + " %s\n" + indent + " %s\n" + indent + "}").format(prelude.mkString("\n " + indent), process(body, indent + " ")) 98 | case _ => throw new CompilerError("let values have to be a list") 99 | } 100 | case _ => throw new CompilerError("let names have to be a list of strings") 101 | } 102 | case "defun" => l(1) match { 103 | case name: String => l(2) match { 104 | case parms: List[Any] => 105 | val p = parms.map { 106 | case n: String => n 107 | case _ => throw new CompilerError("parm names have to be strings") 108 | } 109 | val args = p.map(n => isFunction(n, l(3))).zip(p).zipWithIndex.map { 110 | case ((-1, name: String), _) => 111 | name + (usedInFunction(name, l(3)) match { 112 | case Some(UsedIn(name, pos)) => 113 | if(higher_order_funs.contains(name)) 114 | ": (%s) => Any".format( 115 | (0 until higher_order_funs(name)(pos)).map(_ => "Any").mkString(", ") ) 116 | else 117 | ": Any" 118 | case _ => ": Any" 119 | }) 120 | case ((n: Int, pname: String), i: Int) => 121 | val m = higher_order_funs(name) 122 | m(i) = n 123 | higher_order_funs(name) = m 124 | pname + ": (%s) => Any".format( 125 | (0 until n).map(_ => "Any").mkString(", ") ) 126 | } 127 | global_functions = ("def %s(%s): Any = {\n" + indent + " %s\n" + indent + "}").format(name, 128 | args.mkString(", "), 129 | process(l(3), indent + " ")) :: global_functions 130 | "" 131 | case _ => throw new CompilerError("function arguments have to be a list") 132 | } 133 | case _ => throw new CompilerError("function name has to be a string") 134 | } 135 | 136 | // function call 137 | // replace simple functions by operators for prettier code 138 | case op: String if "+-*/%".contains(op) => l.tail.map(e => process(e)).mkString("(", " %s ".format(op), ")") 139 | case "append" => l.tail.map(e => process(e)).mkString(" ++ ") 140 | case "cons" => l.tail.map(e => process(e)).mkString(" :: ") 141 | case "=" if l.length == 3 => l.tail.map(e => process(e)).mkString(" == ") 142 | case comp: String if List("<", ">", "<=", ">=", "=", "!=").contains(comp) && 143 | l.length == 3 => l.tail.map(e => process(e)).mkString(" %s ".format(comp)) 144 | 145 | // other functions have to be called normally 146 | case name: String => "%s(%s)".format(name, l.tail.map(e => process(e, indent + " ")).mkString(", ")) 147 | } 148 | 149 | case op: String if "+-*/%".contains(op) => "_ %s _".format(op) 150 | case s: String => s 151 | // basic values 152 | case n: Long => n.toString + "l" 153 | case d: Double => d.toString 154 | case Literal(l) => "\"" + l + "\"" 155 | } 156 | 157 | def stringify(exp: Any): String = exp match { 158 | case l: List[Any] => l.map(e => stringify(e)).mkString("List(", ", ", ")") 159 | 160 | case s: String => "\"" + s + "\"" 161 | 162 | // basic values 163 | case n: Long => n.toString + "l" 164 | case d: Double => d.toString 165 | case Literal(l) => "Literal(\"" + l + "\")" 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/scala/completer.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.SortedSet; 7 | import java.util.TreeSet; 8 | import scala.tools.jline; 9 | 10 | 11 | 12 | class StringsCompleter(strs: Iterable[String]) extends jline.console.completer.Completer { 13 | val strings = new TreeSet[String]() 14 | 15 | 16 | import scala.collection.JavaConverters._ 17 | setStrings(strs) 18 | 19 | def setStrings(strs: Iterable[String]) { 20 | strings.clear() 21 | strings.addAll(strs.toList.asJava) 22 | } 23 | 24 | def getStrings() = strings 25 | 26 | def complete(buffer: String, cursor: Int, candidates: java.util.List[CharSequence]): Int = { 27 | 28 | val buf = buffer.substring(1+math.max(buffer.lastIndexOf("("), 29 | buffer.lastIndexOf(" "))) 30 | 31 | if (buffer == null) { 32 | candidates.addAll(strings); 33 | } 34 | else { 35 | 36 | for (m <- strings.tailSet(buf).asScala) { 37 | 38 | if (!m.startsWith(buf)) { 39 | return cursor - buf.length 40 | } 41 | candidates.add(m); 42 | } 43 | } 44 | 45 | if (candidates.size() == 1) { 46 | candidates.set(0, candidates.get(0) + " "); 47 | } 48 | 49 | cursor - buf.length 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/scala/interpreter.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | class InterpreterException(s: String) extends Exception(s) { } 4 | class VariableNotFound(s: String) extends InterpreterException(s) { } 5 | class MethodNotFound(s: String) extends InterpreterException(s) { } 6 | class InvalidName(s: String) extends InterpreterException(s) { } 7 | class TypeError(s: String) extends InterpreterException(s) { } 8 | 9 | case class Function(parms: List[String], body: Any) { 10 | def arity = parms.length 11 | } 12 | 13 | class FunctionTable() { 14 | val fs = collection.mutable.Map[Int, Function]() 15 | def add(f: Function) { fs(f.arity) = f } 16 | def apply(arity: Int): Option[Function] = fs.get(arity) 17 | 18 | override def toString = "Function table: " + fs.mkString("\n") 19 | } 20 | 21 | object Interpreter { 22 | def eval(expression: Any, environment: Env): Any = { 23 | var exp = expression 24 | var env = environment 25 | while(true) { 26 | exp match { 27 | case l: List[Any] => l.head match { 28 | // special forms 29 | case "if" => eval(l(1), env) match { 30 | case true => exp = l(2) 31 | case false => exp = l(3) 32 | } 33 | case "define" => return l(1) match { 34 | case name: String => env.define(name, eval(l(2), env)) 35 | case _ => throw new InvalidName("variable name has to be a string") 36 | } 37 | case "set!" => return l(1) match { 38 | case name: String => env.update(name, eval(l(2), env)) 39 | case _ => throw new InvalidName("variable name has to be a string") 40 | } 41 | case "begin" => 42 | l.tail.init.map(e => eval(e, env)) 43 | exp = l.last 44 | case "quote" => return l(1) 45 | case "lambda" => return l(1) match { 46 | case parms: List[Any] => 47 | val p = parms.map { 48 | case n: String => n 49 | case _ => "parm names have to be strings" 50 | } 51 | Function(p, l(2)) 52 | case _ => throw new InterpreterException("lambda arguments have to be a list") 53 | } 54 | case "defun" => return l(1) match { 55 | case name: String => l(2) match { 56 | case parms: List[Any] => 57 | val p = parms.map { 58 | case n: String => n 59 | case _ => "parm names have to be strings" 60 | } 61 | if(l.length != 4) throw new InterpreterException("function has to have form (defun )") 62 | val f = Function(p, l(3)) 63 | env.define(name, f) 64 | case _ => throw new InterpreterException("function arguments have to be a list") 65 | } 66 | case _ => throw new InvalidName("function name has to be a string") 67 | } 68 | case "let" => return l(1) match { 69 | case names: List[String] => l(2) match { 70 | case vals: List[Any] => 71 | val body = l(3) 72 | val context = new Env(env) 73 | val args = vals.map(e => eval(e, env)) 74 | names.zip(args).foreach { 75 | case (param: String, value: Any) => context.define(param, value) 76 | } 77 | eval(body, context) 78 | case _ => throw new InterpreterException("let values have to be a list") 79 | } 80 | case _ => throw new InterpreterException("let names have to be a list of strings") 81 | } 82 | case n: String if Builtins.builtins(l, env).isDefinedAt(n) => 83 | return Builtins.builtins(l, env)(n) 84 | case s: String => env.getFunction(s, l.tail.length) match { 85 | case None => 86 | val b = Builtins.builtins(l, env) 87 | val fname = env(s) match { 88 | case Some(s: String) => s 89 | case _ => throw new MethodNotFound(s) 90 | } 91 | if( b.isDefinedAt(fname)) { 92 | return b(fname) 93 | } else { 94 | throw new MethodNotFound(s) 95 | } 96 | case Some(Function(parms, body)) => 97 | val context = new Env(env) 98 | val args = l.tail.map(e => eval(e, env)) 99 | parms.zip(args).foreach { 100 | case (param: String, value: Any) => context.define(param, value) 101 | } 102 | exp = body 103 | env = context 104 | } 105 | 106 | case _ => throw new TypeError("can't call non-string function") 107 | } 108 | 109 | case s: String => return env(s) match { 110 | case None => throw new VariableNotFound(s) 111 | case Some(v) => v 112 | } 113 | 114 | // basic values 115 | case n: Long => return n 116 | case d: Double => return d 117 | case Literal(l) => return l 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/scala/parser.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import scala.util.parsing.combinator._ 4 | 5 | case class Literal(l: String) { } 6 | case class Replace(n: String) 7 | 8 | object LispParser extends JavaTokenParsers { 9 | def parse(line: String) = parseAll(program, line) match { 10 | case Success(r, _) => r 11 | } 12 | 13 | // grammar 14 | def program: Parser[List[Any]] = rep(exp) 15 | def list: Parser[List[Any]] = "("~>rep(exp)<~")" 16 | def exp: Parser[Any] = ( 17 | real 18 | | hexInteger 19 | | integer 20 | | quote 21 | | literal 22 | | list 23 | | replacement 24 | | token 25 | ) 26 | def integer: Parser[Long] = wholeNumber ^^ (n => n.toLong) 27 | def real: Parser[Double] = ( """\-?\d+\.\d*([eE]\-?\d+)?""".r ^^ (d => d.toDouble) 28 | | """\-?\d+[eE]\-?\d+""".r ^^ (d => d.toDouble) ) 29 | def hexInteger: Parser[Long] = """\-?0x[\da-fA-F]+""".r ^^ (n => java.lang.Long.parseLong(n.substring(2), 16)) 30 | def token: Parser[String] = """[^() ]+""".r ^^ (n => n.toString) 31 | def literal: Parser[Literal] = stringLiteral ^^ (l => Literal(l.tail.init)) 32 | def quote = "'"~>exp ^^ (e => List("quote", e)) 33 | def replacement = ","~>token ^^ (t => Replace(t)) 34 | } -------------------------------------------------------------------------------- /src/main/scala/preprocessor.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | object Preprocessor { 4 | val macros = collection.mutable.Map[String, Function]() 5 | 6 | def process(ast: List[Any]) = { 7 | val (mac, code) = ast.partition { 8 | case "defmacro" :: body => true 9 | case _ => false 10 | } 11 | mac.foreach { 12 | case List("defmacro", name: String, args: List[String], body) => 13 | macros(name) = Function(args, body) 14 | } 15 | replaceMacros(code) match { 16 | case l: List[Any] => l 17 | } 18 | } 19 | 20 | def replaceMacros(ast: Any): Any = ast match { 21 | case (name: String) :: args if macros.contains(name) => applyMacro(name, args) 22 | case l: List[Any] => l.map(e => replaceMacros(e)) 23 | case e => e 24 | } 25 | 26 | def applyMacro(name: String, args: List[Any]): Any = { 27 | val m = macros(name) 28 | val pars = m.parms.zip(args).map { 29 | case (name, value) => name -> value 30 | } 31 | applyMacroRec(m.body, pars.toMap) 32 | } 33 | 34 | def applyMacroRec(ast: Any, args: Map[String, Any]): Any = ast match { 35 | case Replace(n) => args(n) 36 | case l: List[Any] => l.map(e => applyMacroRec(e, args)) 37 | case e => e 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/scala/scalisp.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import scala.tools.jline 4 | import org.clapper.argot._ 5 | import java.io.File 6 | import java.io.PrintWriter 7 | 8 | class Env(parent: Env) { 9 | val map: collection.mutable.Map[String, Any] = collection.mutable.Map[String, Any]() 10 | 11 | override def toString = map.mkString("\n") 12 | 13 | def update(k: String, v: Any) { 14 | map.get(k) match { 15 | case Some(_) => map(k) = v 16 | case None => parent match { 17 | case null => throw new VariableNotFound(k) 18 | case p => p(k) = v 19 | } 20 | } 21 | } 22 | def define(k: String, v: Any) { 23 | v match { 24 | case f: Function => 25 | map.get(k) match { 26 | case Some(t: FunctionTable) => 27 | t.add(f) 28 | case _ => 29 | val t = new FunctionTable() 30 | t.add(f) 31 | map(k) = t 32 | } 33 | case _ => map(k) = v 34 | } 35 | } 36 | def apply(k: String): Option[Any] = map.get(k) match { 37 | case None => parent match { 38 | case null => None 39 | case _ => parent(k) 40 | } 41 | case v => v 42 | } 43 | 44 | def getFunction(k: String, arity: Int): Option[Function] = map.get(k) match { 45 | case None => parent match { 46 | case null => None 47 | case _ => parent.getFunction(k, arity) 48 | } 49 | case Some(t: FunctionTable) => 50 | t(arity) match { 51 | case None => parent match { 52 | case null => None 53 | case _ => parent.getFunction(k, arity) 54 | } 55 | case f => f 56 | } 57 | case _ => None 58 | } 59 | } 60 | 61 | class REPL { 62 | val defaultEnv = new Env(null) { 63 | override val map = collection.mutable.Map[String, Any]( 64 | "true" -> true, "false" -> false, "unit" -> (), 65 | "+" -> "+", "-" -> "-", "*" -> "*", "/" -> "/", "%" -> "%", 66 | ">" -> ">", ">=" -> ">=", "<" -> "<", "<=" -> "<=", "=" -> "=", "!=" -> "!=", 67 | "min" -> "min", "max" -> "max" 68 | ) 69 | } 70 | 71 | // load builtins defined in lisp 72 | execute(io.Source.fromFile("builtins.lisp").mkString) 73 | 74 | def execute(l: String) = { 75 | val ast = LispParser.parse(l.replaceAll(";[^\n$]*", " ").replace("\n", " ")) 76 | Preprocessor.process(ast).map(e => Interpreter.eval(e, defaultEnv)) 77 | } 78 | 79 | def executeLine(l: String) = { 80 | val r = execute(l) 81 | if(r.length < 1) () else r.last 82 | } 83 | } 84 | 85 | object Scalisp { 86 | import ArgotConverters._ 87 | val parser = new ArgotParser("scalisp", preUsage=Some("Version 1.0")) 88 | val compile = parser.flag[Boolean](List("c", "compile"), 89 | "Compile instead of interpret") 90 | val input = parser.multiParameter[String]("input", 91 | "Input files to read. If not " + 92 | "specified, use stdin.", 93 | true) { 94 | case (s, opt) => 95 | val file = new File(s) 96 | if (! file.exists) 97 | parser.usage("Input file \"" + s + "\" does not exist.") 98 | s 99 | } 100 | 101 | val repl = new REPL() 102 | 103 | val builtinNames = List("car", "cdr", "cons", "append", "list", "shuffle", 104 | "print", "to-string", "concat", "atom") 105 | 106 | def main(args: Array[String]) { 107 | try { 108 | parser.parse(args) 109 | input.value match { 110 | case List() => 111 | val consoleReader = new jline.console.ConsoleReader() 112 | val completer = new StringsCompleter(builtinNames ++ repl.defaultEnv.map.keys) 113 | consoleReader.addCompleter(completer); 114 | Iterator.continually(consoleReader.readLine("scalisp> ")).takeWhile(_ != "").foreach { 115 | case "exit" | null => sys.exit(0) 116 | case line => 117 | try { 118 | var src = line 119 | // make sure expressions are balanced 120 | while(src.count(_ == '(') != src.count(_ == ')')) { 121 | val in = consoleReader.readLine(" | " ) 122 | 123 | if(in == null || in.length == 0) 124 | // this will throw an exception 125 | println(repl.executeLine(src)) 126 | src += " " + in 127 | } 128 | compile.value match { 129 | case Some(true) => println(ScalispCompiler.compileLine(src)) 130 | case _ => 131 | println("=> " + repl.executeLine(src)) 132 | completer.setStrings(repl.defaultEnv.map.keys) 133 | } 134 | } 135 | catch { 136 | case e: InterpreterException => println(e) 137 | case e: MatchError => println(e) 138 | } 139 | } 140 | case l: List[String] => l.foreach { 141 | case filename => 142 | val src = io.Source.fromFile(filename).mkString 143 | compile.value match { 144 | case Some(true) => 145 | val code = ScalispCompiler.compile(src, filename) 146 | val out = new PrintWriter( new File(filename.split('.').init :+ "scala" mkString ".") ) 147 | try { 148 | out.print( code ) 149 | } finally{ 150 | out.close 151 | } 152 | case _ => repl.execute(src) 153 | } 154 | } 155 | } 156 | } catch { 157 | case e: ArgotUsageException => println(e.message) 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/test/scala/BugFixes.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class BugFiexes extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | // https://github.com/Mononofu/Scalisp/issues/1 10 | "Variables set in child scopes" should "change the parent scope too" in { 11 | repl.execute(""" 12 | (define x 3) 13 | (defun f (n) (set! x n)) 14 | """) 15 | repl.executeLine("x") should equal (3) 16 | repl.executeLine(("(f 2)")) 17 | repl.executeLine("x") should equal (2) 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/Compiler.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class Compiler extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Function arities" should "be recognized correctly" in { 10 | val ast = List[Any]("begin", List[Any]("print", 1, 4), List[Any]("echo", 3, 3.0)) 11 | ScalispCompiler.isFunction("print", ast) should equal (2) 12 | ScalispCompiler.isFunction("+", ast) should equal (-1) 13 | 14 | val ast2 = List[Any]("if", 15 | List[Any]("=", List(), "seq"), 16 | List(), 17 | List[Any]("cons", 18 | List[Any]("f", 19 | List[Any]("car", "seq")), 20 | List[Any]("map", 21 | "f", 22 | List[Any]("cdr", "seq")))) 23 | 24 | 25 | ScalispCompiler.isFunction("f", ast2) should equal (1) 26 | 27 | val ast3 = List("foldl", 28 | "f", 29 | List("car", "seq"), 30 | List("cdr", "seq")) 31 | 32 | ScalispCompiler.usedInFunction("f", ast3).get should equal (ScalispCompiler.UsedIn("foldl", 0)) 33 | ScalispCompiler.usedInFunction("car", ast3) should equal (None) 34 | } 35 | 36 | "Compiled builtins" should "still work correctly" in { 37 | val l = CompiledApp.CompiledBuiltins.range(5l) 38 | l should equal (List(0, 1, 2, 3, 4)) 39 | CompiledApp.CompiledBuiltins.length(l) should equal (5l) 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/Constants.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class ConstantSpec extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "A number" should "evaluate to itself" in { 10 | repl.executeLine("5") should equal (5) 11 | repl.executeLine("-7") should equal (-7) 12 | repl.executeLine("75.34") should equal (75.34) 13 | repl.executeLine("8e23") should equal (8e23) 14 | repl.executeLine("-8e-23") should equal (-8e-23) 15 | repl.executeLine("-75.34") should equal (-75.34) 16 | } 17 | 18 | "A string" should "also evaluate to itself" in { 19 | repl.executeLine("\"hello\"") should equal ("hello") 20 | repl.executeLine("\"\"") should equal ("") 21 | } 22 | 23 | "Boolean values" should "work" in { 24 | repl.executeLine("true") should equal (true) 25 | repl.executeLine("false") should equal (false) 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/If.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class IfSpec extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "An If" should "select the first branch if the condition is true" in { 10 | repl.executeLine("(if true 1 0)") should equal (1) 11 | repl.executeLine("(if (< 1 2) 1 0)") should equal (1) 12 | } 13 | 14 | it should "select the second branch if the condition is false" in { 15 | repl.executeLine("(if false 1 0)") should equal (0) 16 | repl.executeLine("(if (= 3 2) 1 0)") should equal (0) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/Interpretion.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class Interpretation extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Functions" should "be quotable" in { 10 | repl.executeLine("'+") should equal ("+") 11 | repl.executeLine("(foldl '+ 0 '(1 2 3 4 5))") should equal (15) 12 | } 13 | 14 | "Multiple Functions with different arities" should "not shadow each other" in { 15 | repl.execute(""" 16 | (defun test (a) 1) 17 | (defun test (a b) 2) 18 | (defun test (a b c) 3) 19 | (define test (lambda (a b c d) 4)) 20 | """) 21 | 22 | evaluating { repl.executeLine("(test)") } should produce [MethodNotFound] 23 | repl.executeLine("(test 0)") should equal (1) 24 | repl.executeLine("(test 0 0)") should equal (2) 25 | repl.executeLine("(test 0 0 0)") should equal (3) 26 | repl.executeLine("(test 0 0 0 0)") should equal (4) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/Lambda.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class LambdaSpec extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Lambdas" should "be allowed" in { 10 | repl.executeLine("(define test (lambda (r) (* 3.141592653 (* r r))))") 11 | } 12 | 13 | it should "execute correctly" in { 14 | repl.executeLine("(define square (lambda (x) (* x x)))") 15 | repl.executeLine("(square 4)") should equal (16) 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/Parsing.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class Parsing extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Statements" should "be able to span multiple lines" in { 10 | repl.executeLine(""" 11 | (+ 12 | 1 13 | 2) 14 | """) should equal (3) 15 | } 16 | 17 | "Comments" should "be ignored" in { 18 | repl.executeLine("(+ 1 2) ; a comment") should equal (3) 19 | repl.execute(""" 20 | ; a multiline comment 21 | ; it goes on 22 | ; and on 23 | (* 2 3) ; more Comments 24 | ; this shouldn't execute: (+ 10 20) 25 | """) should equal (List(6)) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/scala/Procedures.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class ProcedureSpec extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Multi-line code" should "not crash" in { 10 | repl.execute(""" 11 | (define a 1) 12 | (define b 2) 13 | (define l '(1 2 3 4)) 14 | """) 15 | } 16 | 17 | "Addition and substraction" should "work as expected" in { 18 | repl.executeLine("(+ 4 3 5 3)") should equal (15) 19 | repl.executeLine("(+ 10 -3 8)") should equal (15) 20 | 21 | repl.executeLine("(- 10 -3 8)") should equal (5) 22 | 23 | repl.executeLine("(+ a b)") should equal (3) 24 | repl.executeLine("(- a b)") should equal (-1) 25 | } 26 | 27 | it should "still work if only one element is passed" in { 28 | repl.executeLine("(+ 7)") should equal (7) 29 | 30 | repl.executeLine("(- 7)") should equal (7) 31 | } 32 | 33 | "Comparisons" should "work normally" in { 34 | repl.executeLine("(< 2 3)") should equal (true) 35 | repl.executeLine("(< 4 3)") should equal (false) 36 | repl.executeLine("(< 3 3)") should equal (false) 37 | repl.executeLine("(< 1 2 3 4 5)") should equal (true) 38 | repl.executeLine("(< 3 2 1 4)") should equal (false) 39 | 40 | repl.executeLine("(< a b)") should equal (true) 41 | repl.executeLine("(< b a)") should equal (false) 42 | 43 | 44 | repl.executeLine("(> 2 3)") should equal (false) 45 | repl.executeLine("(> 4 3)") should equal (true) 46 | repl.executeLine("(> 3 3)") should equal (false) 47 | repl.executeLine("(> 5 4 3 2 1)") should equal (true) 48 | repl.executeLine("(> 4 2 3 1)") should equal (false) 49 | 50 | repl.executeLine("(> a b)") should equal (false) 51 | repl.executeLine("(> b a)") should equal (true) 52 | 53 | 54 | repl.executeLine("(= 2 3)") should equal (false) 55 | repl.executeLine("(= 3 3)") should equal (true) 56 | repl.executeLine("(= 1 2 3)") should equal (false) 57 | repl.executeLine("(= 3 3 3 3)") should equal (true) 58 | 59 | repl.executeLine("(= a b)") should equal (false) 60 | repl.executeLine("(= a a)") should equal (true) 61 | } 62 | 63 | "car" should "return the head of a list" in { 64 | repl.executeLine("(car '(1 2 3 4))") should equal (1) 65 | repl.executeLine("(car l)") should equal (1) 66 | } 67 | 68 | it should "throw a TypeError on non-lists" in { 69 | evaluating { repl.executeLine("(car 1)") } should produce [TypeError] 70 | } 71 | 72 | "cdr" should "return the tail of a list" in { 73 | repl.executeLine("(cdr '(1 2 3 4))") should equal (List(2, 3, 4)) 74 | repl.executeLine("(cdr l)") should equal (List(2, 3, 4)) 75 | } 76 | 77 | it should "throw a TypeError on non-lists" in { 78 | evaluating { repl.executeLine("(cdr 1)") } should produce [TypeError] 79 | } 80 | 81 | "append" should "merge two or more lists" in { 82 | repl.executeLine("(append '(1 2) '(3 4))") should equal (List(1, 2, 3, 4)) 83 | repl.executeLine("(append '(1 2) '(3 4) '(5 6) '(7 8))") should equal (List(1, 2, 3, 4, 5, 6, 7, 8)) 84 | repl.executeLine("(append l l)") should equal (List(1, 2, 3, 4, 1, 2, 3, 4)) 85 | } 86 | 87 | it should "throw a TypeError on non-lists" in { 88 | evaluating { repl.executeLine("(append 1 2 3)") } should produce [TypeError] 89 | evaluating { repl.executeLine("(append '(1 2) 2 3)") } should produce [TypeError] 90 | } 91 | 92 | "range" should "create a range of numbers" in { 93 | repl.executeLine("(range 5)") should equal (List(0, 1, 2, 3, 4)) 94 | repl.executeLine("(range 5 10)") should equal (List(5, 6, 7, 8, 9)) 95 | } 96 | 97 | "length" should "return the length of a list" in { 98 | repl.executeLine("(length (range 10))") should equal (10) 99 | } 100 | 101 | "map" should "map the values of a list" in { 102 | repl.executeLine("(map (lambda (a) (+ a 5)) (range 5))") should equal (List(5, 6, 7, 8, 9)) 103 | } 104 | 105 | "foldl" should "fold a list" in { 106 | repl.executeLine("(foldl + 0 (range 5))") should equal (10) 107 | repl.executeLine("(foldl * 1 (range 1 5))") should equal (24) 108 | } 109 | 110 | "reduce" should "should act like fold, but with no initial value" in { 111 | repl.executeLine("(reduce + (range 5))") should equal (10) 112 | repl.executeLine("(reduce * (range 1 5))") should equal (24) 113 | } 114 | 115 | "filter" should "filter a list" in { 116 | repl.executeLine("(filter (lambda (a) (> a 5)) (range 10))") should equal ( 117 | List(6, 7, 8, 9)) 118 | } 119 | 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/test/scala/SampleCode.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class SampleCode extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "Factorial" should "work" in { 10 | repl.executeLine("(define fact (lambda (n) (if (< n 2) 1 (* n (fact (- n 1))))))") 11 | 12 | repl.executeLine("(fact 6)") should equal (720) 13 | repl.executeLine("(fact 10)") should equal (3628800) 14 | repl.executeLine("(fact 0)") should equal (1) 15 | } 16 | 17 | "Fibonacci numbers" should "also work" in { 18 | repl.executeLine("(define fib (lambda (n) (if (< n 3) 1 (+ (fib (- n 1)) (fib (- n 2))))))") 19 | 20 | repl.executeLine("(fib 1)") should equal (1) 21 | repl.executeLine("(fib 2)") should equal (1) 22 | repl.executeLine("(fib 10)") should equal (55) 23 | repl.executeLine("(fib 15)") should equal (610) 24 | } 25 | 26 | "Merge sort" should "work" in { 27 | 28 | repl.execute( 29 | """ 30 | (define msort 31 | (lambda (list) 32 | (if (<= (length list) 1) 33 | list 34 | (begin 35 | (define split (/ (length list) 2)) 36 | (merge 37 | (msort (subseq list 0 split)) 38 | (msort (subseq list split)) 39 | ) 40 | ) 41 | ) 42 | ) 43 | ) 44 | 45 | (define merge 46 | (lambda (a b) 47 | (if (< (length a) 1) 48 | b 49 | (if (< (length b) 1) 50 | a 51 | (if (< (car a) (car b)) 52 | (cons (car a) (merge (cdr a) b)) 53 | (cons (car b) (merge a (cdr b))) 54 | ) 55 | ) 56 | ) 57 | ) 58 | )""") 59 | 60 | repl.executeLine("(merge '(1 4) '())") should equal (List(1, 4)) 61 | repl.executeLine("(merge '() '(1 4))") should equal (List(1, 4)) 62 | repl.executeLine("(merge '(1 4) '(2 3))") should equal (List(1, 2, 3, 4)) 63 | 64 | repl.executeLine("(msort '(5 7 2 1 3 4 6))") should equal (List(1, 2, 3, 4, 5, 6, 7)) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/scala/Variables.scala: -------------------------------------------------------------------------------- 1 | package Scalisp 2 | 3 | import org.scalatest.FlatSpec 4 | import org.scalatest.matchers.ShouldMatchers 5 | 6 | class VariableSpec extends FlatSpec with ShouldMatchers { 7 | val repl = new REPL() 8 | 9 | "A variable" should "save its content" in { 10 | repl.executeLine("(define a 5)") 11 | repl.executeLine("a") should equal (5) 12 | 13 | repl.executeLine("(define b (+ a a))") 14 | repl.executeLine("b") should equal (10) 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /test.lisp: -------------------------------------------------------------------------------- 1 | ; this file is a collection of various code to test the intepreter 2 | ; and show how to code for it 3 | 4 | (print "hello world") 5 | 6 | (defun fib (n) (if (< n 3) 1 (+ (fib (- n 1)) (fib (- n 2))))) 7 | 8 | (print "20th fibonacci number: " (fib 10)) 9 | 10 | 11 | ; note that you can also use comments 12 | ; and split functions over multiple lines for readability 13 | (defun msort (list) 14 | (if (<= (length list) 1) 15 | list 16 | (begin 17 | (define split (/ (length list) 2)) 18 | (merge 19 | (msort (subseq list 0 split)) 20 | (msort (subseq list split)) 21 | ) 22 | ) 23 | ) 24 | ) 25 | 26 | ; ordering is not important, as functions are evaluated lazily 27 | (defun merge (a b) 28 | (if (< (length a) 1) 29 | b 30 | (if (< (length b) 1) 31 | a 32 | (if (< (car a) (car b)) 33 | (cons (car a) (merge (cdr a) b)) 34 | (cons (car b) (merge a (cdr b))) 35 | ) 36 | ) 37 | ) 38 | ) 39 | 40 | (define l (shuffle (range 20))) 41 | (print "let's take a random list: " l) 42 | (print "and sort it: " (msort l)) 43 | 44 | (print (length (range 10))) 45 | 46 | (print "test higher-order functions") 47 | (print (map (lambda (a) (* a a)) (range 10))) 48 | (print (reduce * (range 1 10))) 49 | 50 | (print "complex arithmetic") 51 | (print (+ (* (+ 1 2) (- 4 1)) 10)) 52 | 53 | (print "test long/double typing") 54 | (defun echo (n) (+ n 1)) 55 | (print (echo 10)) 56 | (print (echo 20.5)) 57 | 58 | (print "macros") 59 | (defmacro square (X) (* ,X ,X)) 60 | (print (square 5)) 61 | 62 | (defmacro square+ (X) 63 | (let (x) (,X) (* x x))) 64 | (print (square+ (begin (print "executed") 5))) 65 | 66 | 67 | (defun unless_bad (cond exp) 68 | (if cond unit exp)) 69 | (unless_bad (= 5 5) (print "executed branch")) 70 | 71 | (defmacro unless (COND EXP) 72 | (if ,COND unit ,EXP)) 73 | (unless (= 5 5) (print "executed branch")) 74 | 75 | (defun map_good (f l acc) 76 | (if (= l '()) 77 | acc 78 | (map_good f (cdr l) (cons (f (car l)) acc)) 79 | ) 80 | ) 81 | 82 | ;(map_good (lambda (x) (+ x 1)) (range 10000000) '()) 83 | 84 | ; naive sum, will cause stack overflow for n = 10.000 85 | (defun sum_to (n) 86 | (if (= n 0) 87 | 0 88 | (+ n (sum_to (- n 1))))) 89 | 90 | ; tail-call version, won't overflow 91 | (defun sum_to_good (n acc) 92 | (if (= n 0) 93 | acc 94 | (sum_to_good (- n 1) (+ n acc)))) 95 | 96 | (print "tail-recursive sum: " (sum_to_good 10000 0)) 97 | 98 | ; another tail-cail test 99 | ;(subseq (range 10200) 10000 10009) --------------------------------------------------------------------------------