Clean up and initial commit.
This commit is contained in:
commit
67720f4f6a
|
@ -0,0 +1,18 @@
|
|||
*#
|
||||
.#*
|
||||
*~
|
||||
*.orig
|
||||
npm-debug.log
|
||||
node_modules/*
|
||||
bootstrap/*.js
|
||||
notes/
|
||||
tmp/
|
||||
.tup
|
||||
package.yml
|
||||
bootstrap/crisp.js
|
||||
bin/cake
|
||||
bin/coffee
|
||||
bin/escodegen
|
||||
bin/esgenerate
|
||||
bin/mocha
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
Copyright (c) 2014, Elf M. Sternberg
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name of Elf M. Sternberg nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,16 @@
|
|||
.PHONY: test
|
||||
|
||||
# docs: $(patsubst %.md,%.html,$(wildcard *.md))
|
||||
|
||||
%.html: %.md header.html footer.html
|
||||
cat header.html > $@
|
||||
pandoc $< >> $@
|
||||
cat footer.html >> $@
|
||||
|
||||
node_modules: package.json
|
||||
mkdir -p node_modules
|
||||
npm install
|
||||
|
||||
test: node_modules
|
||||
@node_modules/.bin/mocha --compilers coffee:coffee-script/register
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# A simple implementation of Lisp-like cons() lists, using vectors
|
||||
|
||||
## Purpose
|
||||
|
||||
I kinda got tired of my broken list implementations that I was working
|
||||
with in each and every variant of Lisp I wrote while working my way
|
||||
through List In Small Pieces, so I decided to break it out into its own
|
||||
managed repo.
|
||||
|
||||
## LICENSE AND COPYRIGHT NOTICE: NO WARRANTY GRANTED OR IMPLIED
|
||||
|
||||
Copyright (c) 2015 Elf M. Sternberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
- Elf M. Sternberg <elf@pendorwright.com>
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# /bin comes before /node_modules/.bin because sometimes I want to
|
||||
# override the behaviors provided.
|
||||
|
||||
PROJECT_ROOT=`pwd`
|
||||
PATH="$PROJECT_ROOT/bin:$PROJECT_ROOT/node_modules/.bin:$PATH"
|
||||
export PATH
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "cons-lists",
|
||||
"version": "0.0.1",
|
||||
"description": "Cons-style lists for Javascript",
|
||||
"main": "lists.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^2.0.0",
|
||||
"mocha": "^2.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://elfsternberg@elfsternberg.com/home/elfsternberg/repos/cons-lists"
|
||||
},
|
||||
"keywords": [
|
||||
"lisp",
|
||||
"javascript",
|
||||
"coffeescript"
|
||||
],
|
||||
"author": "Elf M. Sternberg <elf.sternberg@gmail.com>",
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
vectorp = (a) -> toString.call(a) == '[object Array]'
|
||||
|
||||
cellp = (a) -> vectorp(a) and a.__list == true
|
||||
|
||||
pairp = (a) -> cellp(a) and (a.length == 2)
|
||||
|
||||
listp = (a) -> (pairp a) and (pairp cdr a)
|
||||
|
||||
recordp = (a) -> Object.prototype.toString.call(a) == '[object Object]'
|
||||
|
||||
nilp = (a) -> cellp(a) and a.length == 0
|
||||
|
||||
nil = (-> l = []; l.__list = true; l)()
|
||||
|
||||
cons = (a, b = nil) ->
|
||||
l = if not (a?) then [] else if (nilp a) then b else [a, b]
|
||||
l.__list = true
|
||||
l
|
||||
|
||||
car = (a) -> a[0]
|
||||
|
||||
cdr = (a) -> a[1]
|
||||
|
||||
vectorToList = (v, p) ->
|
||||
p = if p? then p else 0
|
||||
if p >= v.length then return nil
|
||||
# Annoying, but since lists are represented as nested arrays, they
|
||||
# have to be intercepted first. The use of duck-typing here is
|
||||
# frustrating, but I suppose the eventual runtime will be doing
|
||||
# something like this anyway for base types.
|
||||
item = if pairp(v[p]) then v[p] else if vectorp(v[p]) then vectorToList(v[p]) else v[p]
|
||||
cons(item, vectorToList(v, p + 1))
|
||||
|
||||
list = (v...) ->
|
||||
ln = v.length;
|
||||
(nl = (a) ->
|
||||
cons(v[a], if (a < ln) then (nl(a + 1)) else nil))(0)
|
||||
|
||||
listToVector = (l, v = []) ->
|
||||
return v if nilp l
|
||||
v.push if pairp (car l) then listToVector(car l) else (car l)
|
||||
listToVector (cdr l), v
|
||||
|
||||
# This is the simplified version. It can't be used stock with reader,
|
||||
# because read() returns a rich version decorated with extra
|
||||
# information.
|
||||
|
||||
metacadr = (m) ->
|
||||
seq = m.match(/c([ad]+)r/)[1].split('')
|
||||
return (l) ->
|
||||
inner = (l, s) ->
|
||||
return nil if nilp l
|
||||
return l if s.length == 0
|
||||
inner ((if s.pop() == 'a' then car else cdr)(l)), s
|
||||
inner(l, seq)
|
||||
|
||||
module.exports =
|
||||
cons: cons
|
||||
nil: nil
|
||||
car: car
|
||||
cdr: cdr
|
||||
list: list
|
||||
nilp: nilp
|
||||
pairp: pairp
|
||||
vectorp: vectorp
|
||||
recordp: recordp
|
||||
vectorToList: vectorToList
|
||||
listToVector: listToVector
|
||||
setcar: (a, l) -> l[0] = a; a
|
||||
setcdr: (a, l) -> l[1] = a; a
|
||||
cadr: (l) -> car (cdr l)
|
||||
cddr: (l) -> cdr (cdr l)
|
||||
cdar: (l) -> cdr (car l)
|
||||
caar: (l) -> car (car l)
|
||||
caddr: (l) -> car (cdr (cdr l))
|
||||
cdddr: (l) -> cdr (cdr (cdr l))
|
||||
cadar: (l) -> car (cdr (car l))
|
||||
cddar: (l) -> cdr (cdr (car l))
|
||||
caadr: (l) -> car (car (cdr l))
|
||||
cdadr: (l) -> cdr (car (cdr l))
|
||||
metacadr: metacadr
|
|
@ -0,0 +1,60 @@
|
|||
{car, cdr, cons, listp, pairp, nilp, nil, list, listToString} = require './lists'
|
||||
|
||||
reduce = (lst, iteratee, memo, context) ->
|
||||
count = 0
|
||||
return memo if nilp lst
|
||||
memo = iteratee.call(context, memo, (car lst), count)
|
||||
lst = cdr lst
|
||||
count++
|
||||
while not nilp lst
|
||||
memo = iteratee.call(context, memo, (car lst), count)
|
||||
count++
|
||||
lst = cdr lst
|
||||
null
|
||||
memo
|
||||
|
||||
map = (lst, iteratee, context) ->
|
||||
return nil if nilp lst
|
||||
root = cons()
|
||||
|
||||
reducer = (memo, item, count) ->
|
||||
next = cons(iteratee.call(context, item, count, lst))
|
||||
memo[1] = next
|
||||
next
|
||||
|
||||
reduce(lst, reducer, root, context)
|
||||
(cdr root)
|
||||
|
||||
rmap = (lst, iteratee, context) ->
|
||||
return nil if nilp lst
|
||||
root = cons()
|
||||
|
||||
reducer = (memo, item, count) ->
|
||||
cons(iteratee.call(context, item, count, lst), memo)
|
||||
|
||||
reduce(lst, reducer, root, context)
|
||||
|
||||
filter = (lst, iteratee, context) ->
|
||||
return nil if nilp lst
|
||||
root = cons()
|
||||
|
||||
reducer = (memo, item, count) ->
|
||||
if iteratee.call(context, item, count, lst)
|
||||
next = cons(item)
|
||||
memo[1] = next
|
||||
next
|
||||
else
|
||||
memo
|
||||
|
||||
reduce(lst, reducer, root, context)
|
||||
if (pairp root) then (cdr root) else root
|
||||
|
||||
reverse = (lst) -> reduce(lst, ((memo, value) -> cons(value, memo)), cons())
|
||||
|
||||
module.exports =
|
||||
reduce: reduce
|
||||
map: map
|
||||
rmap: rmap
|
||||
filter: filter
|
||||
reverse: reverse
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
chai = require 'chai'
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
|
||||
{listToVector, vectorToList, cons, list, nil, metacadr} = require '../src/lists'
|
||||
|
||||
describe "Basic list building", ->
|
||||
for [t, v] in [
|
||||
[cons(), cons()]
|
||||
[cons(nil), cons()]
|
||||
[cons(), cons(nil)]
|
||||
[cons('a'), cons('a')]
|
||||
[cons('a', cons('b', cons('c'))), cons('a', cons('b', cons('c')))]
|
||||
[cons('a', cons('b', cons('c'))), cons('a', cons('b', cons('c', nil)))]
|
||||
[cons('a', cons('b', cons('c', nil))), cons('a', cons('b', cons('c')))]
|
||||
[cons(nil, cons('a')), cons('a')] # Test for identity; consing nil to anything results in anything
|
||||
[cons(nil, cons(nil, cons(nil))), nil]]
|
||||
do (t, v) ->
|
||||
it "should match #{t}", ->
|
||||
expect(t).to.deep.equal(v)
|
||||
|
||||
describe 'Round trip equivalence', ->
|
||||
for [t, v] in [
|
||||
[[], []]
|
||||
[['a'], ['a']]
|
||||
[['a', 'b'], ['a', 'b']]
|
||||
[['a', 'b', 'c'], ['a', 'b', 'c']]]
|
||||
do (t, v) ->
|
||||
it "should successfully round-trip #{t}", ->
|
||||
expect(listToVector vectorToList t).to.deep.equal(v)
|
||||
|
||||
describe 'List Building', ->
|
||||
for [t, v] in [
|
||||
[cons(), []]
|
||||
[cons(nil), []]
|
||||
[cons('a'), ['a']]
|
||||
[cons('a', cons('b')), ['a', 'b']]
|
||||
[cons('a', cons('b', cons('c'))), ['a', 'b', 'c']]
|
||||
[cons('a', cons('b', cons('c'), nil)), ['a', 'b', 'c']]]
|
||||
do (t, v) ->
|
||||
it "should cons a list into #{v}", ->
|
||||
expect(listToVector t).to.deep.equal(v)
|
||||
|
||||
describe 'Dynamic list constructor', ->
|
||||
for [t, v] in [
|
||||
[list(), []]
|
||||
[list('a'), ['a']]
|
||||
[list('a', 'b'), ['a', 'b']]
|
||||
[list('a', 'b', 'c'), ['a', 'b', 'c']]]
|
||||
do (t, v) ->
|
||||
it "should round trip list arguments into #{v}", ->
|
||||
expect(listToVector t).to.deep.equal(v)
|
||||
|
||||
mcsimple = cons('a', cons('b', cons('c')))
|
||||
|
||||
describe 'Metacadr Simple', ->
|
||||
for [t, v, r] in [
|
||||
['car', 'a']
|
||||
['cadr', 'b']
|
||||
['caddr', 'c']]
|
||||
do (t, v) ->
|
||||
it "The #{t} should read #{v}", ->
|
||||
expect(metacadr(t)(mcsimple)).to.equal(v)
|
||||
|
||||
mccomplex = vectorToList([['a', 'b', 'c'], ['1', '2', '3'], ['X', 'Y', 'Z'], ['f', 'g', 'h']])
|
||||
|
||||
describe 'Metacadr Complex', ->
|
||||
for [t, v, r] in [
|
||||
['cadar', 'b']
|
||||
['caadddr', 'f']
|
||||
['caadr', '1']
|
||||
['caaddr', 'X']]
|
||||
do (t, v) ->
|
||||
it "The #{t} should read #{v}", ->
|
||||
expect(metacadr(t)(mccomplex)).to.equal(v)
|
|
@ -0,0 +1,56 @@
|
|||
chai = require 'chai'
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
|
||||
{listToVector, vectorToList, listToString, cons, list, nil} = require '../src/lists'
|
||||
{map, reduce, filter, reverse} = require '../src/reduce'
|
||||
|
||||
id = (item) -> item
|
||||
|
||||
describe 'Map Identity Testing', ->
|
||||
|
||||
samples = [
|
||||
cons(),
|
||||
cons(nil),
|
||||
cons('a'),
|
||||
cons('a', cons('b'))
|
||||
cons('a', cons('b', cons('c'))),
|
||||
cons('a', cons('b', cons('c'), nil))]
|
||||
|
||||
for t in samples
|
||||
do (t) ->
|
||||
it "should produce the same thing as #{t}", ->
|
||||
product = map(t, id)
|
||||
expect(product).to.deep.equal(t)
|
||||
|
||||
describe 'Filter Testing Testing', ->
|
||||
|
||||
samples = [
|
||||
[vectorToList([]), nil],
|
||||
[vectorToList([1]), nil],
|
||||
[vectorToList([1, 2]), cons(2)],
|
||||
[vectorToList([1, 2, 3]), cons(2)],
|
||||
[vectorToList([1, 2, 3 ,4]), cons(2, cons(4))]]
|
||||
|
||||
truth = (item) -> item % 2 == 0
|
||||
|
||||
for [t, v] in samples
|
||||
do (t, v) ->
|
||||
it "should produce the same thing as #{v}", ->
|
||||
product = filter(t, truth)
|
||||
expect(product).to.deep.equal(v)
|
||||
|
||||
describe 'Reverse', ->
|
||||
|
||||
samples = [
|
||||
[cons(), cons()]
|
||||
[cons(nil), cons(nil)]
|
||||
[cons('a'), cons('a')]
|
||||
[cons('a', cons('b')), cons('b', cons('a'))]
|
||||
[cons('a', cons('b', cons('c'))), cons('c', cons('b', cons('a')))]]
|
||||
|
||||
for [t, v] in samples
|
||||
do (t, v) ->
|
||||
it "#{t} should produce a reverse of #{v}", ->
|
||||
product = reverse(t)
|
||||
expect(product).to.deep.equal(v)
|
Loading…
Reference in New Issue