lasp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +1 -0
- data/bin/lasp +8 -0
- data/bin/lasp-repl +16 -0
- data/lasp.gemspec +23 -0
- data/lib/lasp/corelib.rb +18 -0
- data/lib/lasp/env.rb +9 -0
- data/lib/lasp/eval.rb +37 -0
- data/lib/lasp/parser.rb +47 -0
- data/lib/lasp/stdlib.lasp +122 -0
- data/lib/lasp/version.rb +3 -0
- data/lib/lasp.rb +18 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c3c2a2b3d78d531e4669760374632562219a5114
|
4
|
+
data.tar.gz: cea95705d76894736b21d1dac3c24691c606f417
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7162a39c7d3000b3d1e20e93b0386fc9e60f7e1d793eb73a9b966be4c69f217c0a9bb21567649d2d5576700bde1995a85bbf5189de2b083c95bc5d645d0ed53e
|
7
|
+
data.tar.gz: d7266dd23f4f0e3ca1b547add6c8ced523c9b54ec10adfc547ae46177590e6908a24784bbac9a15416ce4ba3068fb468b5c989101caa1fe49f6de754a3fff69b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jimmy Börjesson
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Läsp
|
2
|
+
|
3
|
+
A very simple Lisp implementation in Ruby. Run `lasp-repl` to play around with
|
4
|
+
it, or `lasp path/to/program.lasp` to execute a Läsp file.
|
5
|
+
|
6
|
+
## The language
|
7
|
+
|
8
|
+
### Examples
|
9
|
+
|
10
|
+
```lisp
|
11
|
+
(+ 1 2 3)
|
12
|
+
; => 6
|
13
|
+
|
14
|
+
(def x 5)
|
15
|
+
x
|
16
|
+
; => 6
|
17
|
+
|
18
|
+
(def inc (fn (x) (+ x 1)))
|
19
|
+
(inc 5)
|
20
|
+
; => 6
|
21
|
+
```
|
22
|
+
|
23
|
+
### Data types
|
24
|
+
|
25
|
+
Supports these datatypes (implemented as their Ruby counterparts)
|
26
|
+
|
27
|
+
- integer
|
28
|
+
- float
|
29
|
+
- boolean
|
30
|
+
- nil
|
31
|
+
- string
|
32
|
+
|
33
|
+
### Comments
|
34
|
+
|
35
|
+
Comments start with a `;` and end at the end of a line
|
36
|
+
|
37
|
+
```lisp
|
38
|
+
; This is a comment
|
39
|
+
(+ 1 2) ; This is also a comment
|
40
|
+
```
|
41
|
+
|
42
|
+
### Functions in corelib
|
43
|
+
|
44
|
+
Implemented as Ruby lambdas.
|
45
|
+
|
46
|
+
- `+`
|
47
|
+
- `-`
|
48
|
+
- `*`
|
49
|
+
- `/`
|
50
|
+
- `<`
|
51
|
+
- `>`
|
52
|
+
- `=`
|
53
|
+
- `head`
|
54
|
+
- `tail`
|
55
|
+
- `cons`
|
56
|
+
- `not`
|
57
|
+
- `println`
|
58
|
+
|
59
|
+
### Special forms
|
60
|
+
|
61
|
+
Implemented as special cases while evaluating.
|
62
|
+
|
63
|
+
- `def`
|
64
|
+
- `fn`
|
65
|
+
- `do`
|
66
|
+
- `if`
|
67
|
+
|
68
|
+
### Functions in stdlib
|
69
|
+
|
70
|
+
Implemented in Läsp itself.
|
71
|
+
|
72
|
+
- `first` (alias of `head`)
|
73
|
+
- `rest` (alias of `tail`)
|
74
|
+
- `inc`
|
75
|
+
- `dec`
|
76
|
+
- `empty?`
|
77
|
+
- `mod`
|
78
|
+
- `complement`
|
79
|
+
- `even?`
|
80
|
+
- `odd?`
|
81
|
+
- `len`
|
82
|
+
- `nth`
|
83
|
+
- `last`
|
84
|
+
- `reverse`
|
85
|
+
- `map`
|
86
|
+
- `reduce`
|
87
|
+
- `filter`
|
88
|
+
- `sum`
|
89
|
+
- `take`
|
90
|
+
- `drop`
|
91
|
+
- `range`
|
92
|
+
- `max`
|
93
|
+
- `min`
|
94
|
+
|
95
|
+
## Developing
|
96
|
+
|
97
|
+
Run the tests with `rspec`.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/lasp
ADDED
data/bin/lasp-repl
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "lasp"
|
4
|
+
Lasp::load_stdlib!
|
5
|
+
|
6
|
+
trap("SIGINT") { puts "\n\nBye!"; exit! }
|
7
|
+
|
8
|
+
loop do
|
9
|
+
begin
|
10
|
+
print "lasp> "
|
11
|
+
result = Lasp::execute(gets.chomp)
|
12
|
+
puts " =>> #{result.inspect}"
|
13
|
+
rescue
|
14
|
+
puts " **> #{$!}"
|
15
|
+
end
|
16
|
+
end
|
data/lasp.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lasp/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lasp"
|
8
|
+
spec.version = "0.1.0"
|
9
|
+
spec.authors = ["Jimmy Börjesson"]
|
10
|
+
spec.email = ["lagginglion@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A simple programming language similar to Clojure, but much worse.}
|
13
|
+
spec.homepage = "https://github.com/alcesleo/lasp"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "bin"
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
data/lib/lasp/corelib.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Lasp
|
2
|
+
CORELIB = {
|
3
|
+
:+ => -> (_, *args) { args.reduce(:+) },
|
4
|
+
:- => -> (_, *args) { args.reduce(:-) },
|
5
|
+
:* => -> (_, *args) { args.reduce(:*) },
|
6
|
+
:/ => -> (_, *args) { args.reduce(:/) },
|
7
|
+
:< => -> (_, *args) { args.sort == args },
|
8
|
+
:> => -> (_, *args) { args.sort.reverse == args },
|
9
|
+
:"=" => -> (_, *args) { args.uniq.count == 1 },
|
10
|
+
:list => -> (_, *args) { args },
|
11
|
+
:head => -> (_, list) { list.first },
|
12
|
+
:tail => -> (_, list) { list.drop(1) },
|
13
|
+
:cons => -> (_, item, list) { [item] + list },
|
14
|
+
:not => -> (_, arg) { !arg },
|
15
|
+
:println => -> (_, output) { puts output },
|
16
|
+
:"." => -> (_, obj, meth) { obj.send(meth) }
|
17
|
+
}
|
18
|
+
end
|
data/lib/lasp/env.rb
ADDED
data/lib/lasp/eval.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "lasp/parser"
|
2
|
+
require "lasp/env"
|
3
|
+
|
4
|
+
module Lasp
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def eval(ast, env)
|
8
|
+
case ast
|
9
|
+
when Symbol then env.fetch(ast)
|
10
|
+
when Array then eval_form(ast, env)
|
11
|
+
else ast
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def eval_form(form, env)
|
16
|
+
head, *tail = *form
|
17
|
+
|
18
|
+
if head == :def
|
19
|
+
key, value = tail
|
20
|
+
env[key] = eval(value, env)
|
21
|
+
elsif head == :fn
|
22
|
+
params, func = tail
|
23
|
+
# Use env from context to properly scope closures
|
24
|
+
-> (_, *args) { eval(func, env.merge(Hash[params.zip(args)])) }
|
25
|
+
elsif head == :do
|
26
|
+
tail.each do |form| eval(form, env) end
|
27
|
+
elsif head == :if
|
28
|
+
conditional, true_form, false_form = tail
|
29
|
+
eval(conditional, env) ? eval(true_form, env) : eval(false_form, env)
|
30
|
+
elsif Proc === head
|
31
|
+
head.(env, *tail)
|
32
|
+
else
|
33
|
+
fn = eval(head, env)
|
34
|
+
fn.(env, *tail.map { |form| eval(form, env) })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/lasp/parser.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Lasp
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def parse(program)
|
5
|
+
build_ast(tokenize(sanitize(program)))
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_ast(tokens)
|
9
|
+
return if tokens.empty?
|
10
|
+
token = tokens.shift
|
11
|
+
|
12
|
+
if token == "("
|
13
|
+
form = []
|
14
|
+
while tokens.first != ")"
|
15
|
+
form << build_ast(tokens)
|
16
|
+
end
|
17
|
+
tokens.shift
|
18
|
+
form
|
19
|
+
else
|
20
|
+
atom(token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tokenize(string)
|
25
|
+
string
|
26
|
+
.gsub("(", " ( ")
|
27
|
+
.gsub(")", " ) ")
|
28
|
+
.scan(/(?:[^\s"]|"[^"]*")+/)
|
29
|
+
end
|
30
|
+
|
31
|
+
def atom(token)
|
32
|
+
case token
|
33
|
+
when "true" then true
|
34
|
+
when "false" then false
|
35
|
+
when "nil" then nil
|
36
|
+
when /\A\d+\z/ then Integer(token)
|
37
|
+
when /\A\d+.\d+\z/ then Float(token)
|
38
|
+
when /"(.*)"/ then String($1)
|
39
|
+
when /:(\w+)/ then String($1) # Symbol style strings are actually just strings
|
40
|
+
else token.to_sym
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sanitize(string)
|
45
|
+
string.gsub(/;.*$/, "")
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
(do
|
2
|
+
; Aliases
|
3
|
+
(def first head)
|
4
|
+
(def rest tail)
|
5
|
+
|
6
|
+
; Increment a number by one
|
7
|
+
(def inc (fn (x) (+ x 1)))
|
8
|
+
|
9
|
+
; Decrement a number by one
|
10
|
+
(def dec (fn (x) (- x 1)))
|
11
|
+
|
12
|
+
; If a list is empty
|
13
|
+
(def empty?
|
14
|
+
(fn (coll)
|
15
|
+
(= (head coll) nil)))
|
16
|
+
|
17
|
+
; Modulus
|
18
|
+
(def mod
|
19
|
+
(fn (x y) (- x (* (/ x y) y))))
|
20
|
+
|
21
|
+
; Returns a function that does the opposite of the given function
|
22
|
+
(def complement
|
23
|
+
(fn (f) (fn (x) (not (f x)))))
|
24
|
+
|
25
|
+
; If a number is even
|
26
|
+
(def even?
|
27
|
+
(fn (x) (= (mod x 2) 0)))
|
28
|
+
|
29
|
+
; If a number is odd
|
30
|
+
(def odd? (complement even?))
|
31
|
+
|
32
|
+
; Length of a list
|
33
|
+
(def len
|
34
|
+
(fn (coll)
|
35
|
+
(if (empty? coll)
|
36
|
+
0
|
37
|
+
(inc (len (tail coll))))))
|
38
|
+
|
39
|
+
; Gets an item in a list by index
|
40
|
+
(def nth
|
41
|
+
(fn (index coll)
|
42
|
+
(if (= 0 index)
|
43
|
+
(head coll)
|
44
|
+
(nth (dec index) (tail coll)))))
|
45
|
+
|
46
|
+
; Last item in list
|
47
|
+
(def last
|
48
|
+
(fn (coll)
|
49
|
+
(nth (dec (len coll)) coll)))
|
50
|
+
|
51
|
+
; Reverses a list
|
52
|
+
(def reverse
|
53
|
+
(fn (coll)
|
54
|
+
(reduce (fn (acc item) (cons item acc)) (list) coll)))
|
55
|
+
|
56
|
+
; Apply f to all items in list
|
57
|
+
(def map
|
58
|
+
(fn (f coll)
|
59
|
+
(if (= nil (head coll))
|
60
|
+
coll
|
61
|
+
(cons
|
62
|
+
(f (head coll))
|
63
|
+
(map f (tail coll))))))
|
64
|
+
|
65
|
+
; Go through a list passing an accumulator and each item of the list through f
|
66
|
+
; f(acc item)
|
67
|
+
(def reduce
|
68
|
+
(fn (f acc coll)
|
69
|
+
(if (empty? coll)
|
70
|
+
acc
|
71
|
+
(reduce f (f acc (head coll)) (tail coll)))))
|
72
|
+
|
73
|
+
; Fil er a list of items based on a function
|
74
|
+
(def filter
|
75
|
+
(fn (f coll)
|
76
|
+
(reduce
|
77
|
+
(fn (acc item) (if (f item) (cons item acc) acc))
|
78
|
+
(list)
|
79
|
+
(reverse coll))))
|
80
|
+
|
81
|
+
; Sum of all items in a list
|
82
|
+
(def sum
|
83
|
+
(fn (coll)
|
84
|
+
(reduce + 0 coll)))
|
85
|
+
|
86
|
+
; Take x items from list
|
87
|
+
(def take
|
88
|
+
(fn (num coll)
|
89
|
+
(if (= num 0)
|
90
|
+
(list)
|
91
|
+
(cons (head coll) (take (dec num) (tail coll))))))
|
92
|
+
|
93
|
+
; Drop x items from list
|
94
|
+
(def drop
|
95
|
+
(fn (num coll)
|
96
|
+
(if (= num 0)
|
97
|
+
coll
|
98
|
+
(drop (dec num) (tail coll)))))
|
99
|
+
|
100
|
+
; Exclusive range
|
101
|
+
(def range
|
102
|
+
(fn (from to)
|
103
|
+
(if (= from to)
|
104
|
+
(list)
|
105
|
+
(cons from (range (inc from) to)))))
|
106
|
+
|
107
|
+
; Highest value in list
|
108
|
+
(def max
|
109
|
+
(fn (coll)
|
110
|
+
(reduce
|
111
|
+
(fn (acc item) (if (< acc item) item acc))
|
112
|
+
(head coll)
|
113
|
+
(tail coll))))
|
114
|
+
|
115
|
+
; Lowest value in list
|
116
|
+
(def min
|
117
|
+
(fn (coll)
|
118
|
+
(reduce
|
119
|
+
(fn (acc item) (if (> acc item) item acc))
|
120
|
+
(head coll)
|
121
|
+
(tail coll))))
|
122
|
+
)
|
data/lib/lasp/version.rb
ADDED
data/lib/lasp.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "lasp/version"
|
2
|
+
require "lasp/eval"
|
3
|
+
|
4
|
+
module Lasp
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def execute_file(path)
|
8
|
+
execute(File.read(path))
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(program, env = global_env)
|
12
|
+
Lasp::eval(Lasp::parse(program), env)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_stdlib!
|
16
|
+
Lasp::execute_file(File.expand_path("../lasp/stdlib.lasp", __FILE__))
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lasp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jimmy Börjesson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- lagginglion@gmail.com
|
44
|
+
executables:
|
45
|
+
- lasp
|
46
|
+
- lasp-repl
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- Gemfile
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- bin/lasp
|
56
|
+
- bin/lasp-repl
|
57
|
+
- lasp.gemspec
|
58
|
+
- lib/lasp.rb
|
59
|
+
- lib/lasp/corelib.rb
|
60
|
+
- lib/lasp/env.rb
|
61
|
+
- lib/lasp/eval.rb
|
62
|
+
- lib/lasp/parser.rb
|
63
|
+
- lib/lasp/stdlib.lasp
|
64
|
+
- lib/lasp/version.rb
|
65
|
+
homepage: https://github.com/alcesleo/lasp
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.4.5
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: A simple programming language similar to Clojure, but much worse.
|
89
|
+
test_files: []
|