In 1991, Simon Peyton Jones and David Lester wrote a 300-page book on
how to implement functional programming languges, including full
source code in Miranda.  Miranda is a dead language, and I don't have
a Haskell system handy on this machine, so here's my effort at
transliterating the first 30 pages (so far) into JavaScript.

<html><head><title>The Core Language</title>
<script type="text/javascript">
// First, JavaScript's toString behavior is totally fucked.  So:

function map(func, list) {
    var rv = []
    for (var ii = 0; ii < list.length; ii++) {
        rv.push(func(list[ii]))
    }
    return rv
}

function repr(obj) {
    var undefined
    if (obj === undefined) { return "undefined" }
    else if (obj == null) { return "null" }
    else if (obj.__repr__) { return obj.__repr__() }
    else if (obj.constructor == String) { 
        return '"' + obj.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"'
    } else if (obj.constructor == Array) {
        return '[' + map(repr, obj).join(', ') + ']'
    } else {
        return obj.toString()
    }
}

// Miranda has built in the ability to make 'constructors' very
// easily.  Here we add code to emulate that ability somewhat, so that
// where the book has 'EVar "x"', we can write 'EVar("x")', and where
// the book has 'data Expr a = EVar Name', we can write 
// 'EVar = Constructor("EVar", ["Name"])'.  However, I think
// pattern-matching in JavaScript is a lost cause, so if you have an
// EVar x, to get its name, you say x.Name.
function ConstructorObject(name, argnames, argvals) {
    this.tagname = name
    for (var ii = 0; ii < argnames.length; ii++) {
        this[argnames[ii]] = argvals[ii]
    }
    this.argnames = argnames
}
ConstructorObject.prototype = {
    toString: function() { 
        var rv = this.tagname
        for (var ii = 0; ii < this.argnames.length; ii++) {
            rv = rv + ' ' + this[this.argnames[ii]]
        }
        return rv
    },
    __repr__: function() {
        var self = this
        return this.tagname + '(' + map(repr, this.argvals).join(', ') + 
            ')'
    },
    argvals: function() {
        var self = this
        return map(function(name) { return self[name] }, self.argnames)
    }
}
function ArgumentsMismatch(argvals, argnames) {
    this.argvals = argvals
    this.argnames = argnames
    this.toString = function () { 
        return ('ArgumentsMismatch: got: ' + repr(this.argvals) + 
                ' expected: ' + repr(this.argnames))
    }
}
function Constructor(name, argnames) {
    return function() {
        if (arguments.length != argnames.length) {
            throw new ArgumentsMismatch(arguments, argnames)
        }
        return new ConstructorObject(name, argnames, arguments)
    }
}

// Maybe pattern-matching isn't a lost cause.  I added this around
// section 1.5.5 so only the later code uses it.  Basically I got sick
// of not getting error messages when I forgot a case or passed a
// value of the wrong type to a case analysis.

function case_analysis(value, cases) {
    var closure = cases['on_' + value.tagname]
    if (!closure) closure = cases['default']
    if (!closure) throw "Missing case for " + value.tagname
    return closure.apply(this, value.argvals())
}

// So here's the first bit of code from the book, in section 1.3,
// which defines the type Expr a as an algebraic type.

EVar = Constructor("EVar", ["Name"])
ENum = Constructor("ENum", ["Value"])
EConstr = Constructor("EConstr", ["Tag", "Arity"])
EAp = Constructor("EAp", ["Function", "Argument"])
ELet = Constructor("ELet", ["IsRec", "Definitions", "Body"])
ECase = Constructor("ECase", ["Expr", "Alternatives"])
ELam = Constructor("ELam", ["Arguments", "Body"])

// There's a little more code in the section:

function bindersOf(defns) { return map(function(x) { x[0] }, defns) }
function rhssOf(defns) { return map(function(x) { x[1] }, defns) }

// I'm making Alter a constructor rather than using tuples (or, in
// JavaScript, Arrays):
Alter = Constructor("Alter", ["Tag", "Variables", "Body"])

function isAtomicExpr(expr) {
    return (expr.tagname == 'EVar' || expr.tagname == 'ENum')
}

// ScDefn too gets to be a constructor
ScDefn = Constructor("ScDefn", ["Name", "Arguments", "Body"])

small_program_on_p_20 = (
[ScDefn("main", [], EAp(EVar("double"), ENum(21))),
 ScDefn("double", ["x"], EAp(EAp(EVar("+"), EVar("x")), EVar("x")))]
)

PreludeDefs = [
    ScDefn("I", ["x"], EVar("x")),
    ScDefn("K", ["x", "y"], EVar("x")),
    ScDefn("K1", ["x", "y"], EVar("y")),
    ScDefn("S", ["f", "g", "x"], EAp(EAp(EVar("f"), EVar("x")),
                                     EAp(EVar("g"), EVar("x")))),
    ScDefn("compose", ["f", "g", "x"], EAp(EVar("f"),
                                           EAp(EVar("g"), EVar("x")))),
    ScDefn("twice", ["f"], EAp(EAp(EVar("compose"), EVar("f")), EVar("f"))),
] 

function pprExpr_section_1_5_1(expr) {
    if (expr.tagname == 'ENum') { return expr.Value.toString() }
    else if (expr.tagname == 'EVar') { return expr.Name }
    else if (expr.tagname == 'EAp') { 
        return pprExpr_section_1_5_1(expr.Function) + ' ' + 
            pprExpr_section_1_5_1(expr.Argument)
    }
}

function pprAExpr(e) {
    if (isAtomicExpr(e)) return pprExpr(e)
    else return "(" + pprExpr(e) + ")"
}

function repeat(n, x) {
    var rv = []
    for (var ii = 0; ii < n; ii++) rv.push(x)
    return rv
}

function foldll(func, start, list) {
    var rv = start
    for (var ii = 0; ii < list.length; ii++) rv = func(rv, list[ii])
    return rv
}

function mkMultiAp(n, e1, e2) {
    // the Miranda version of this starts out by constructing an
    // infinite list of e2s, which is more difficult to do in JavaScript
    return foldll(EAp, e1, repeat(n, e2))
}

// at this point they justify introducing section 1.5.2 by saying,
// A pretty-printer whose cost is quadratic in the size of the program to
// be printed is clearly unacceptable, so we had better find a way around
// it.

// which may be true in the abstract, but mkMultiAp(990, EVar("f"),
// EVar("x")) still pprExprs quite quickly enough for me with the
// version in the last section, and my JavaScript interpreter seems to
// have a thousand-level stack limit.

function pprExpr(expr) {
    // they forgot ENum in this version...
    if (expr.tagname == 'ENum') { return iStr(expr.Value.toString()) }
    else if (expr.tagname == 'EVar') { return iStr(expr.Name) }
    else if (expr.tagname == 'EAp') { 
        return iAppend(pprExpr(expr.Function), 
                       iAppend(iStr(" "), pprAExpr(expr.Argument)))
    } else if (expr.tagname == 'ELet') {
        var keyword = (expr.IsRec ? "letrec" : "let")
        return iConcat([iStr(keyword), iNewline,
                        iStr("  "), iIndent(pprDefns(expr.Definitions)),
                        iNewline,
                        iStr("in "), pprExpr(expr.Body)])
    } else throw expr.tagname
}

function pprDefns(defns) {
    var sep = iConcat([iStr(";"), iNewline])
    return iInterleave(sep, map(pprDefn, defns))
}

function pprDefn(definition) {
    return iConcat([iStr(definition[0]), iStr(" = "), 
                   iIndent(pprExpr(definition[1]))])
}

// exercise 1.2
function iConcat(iseqs) {
    return foldll(iAppend, iNil, iseqs)
}
function iInterleave(separator, iseqs) {
    if (!iseqs.length) return iNil
    var rv = iseqs.shift()
    while (iseqs.length) rv = iAppend(rv, iAppend(separator, iseqs.shift()))
    return rv
}

function pprint(prog) {
    return iDisplay(pprProgram(prog))
}

// Section 1.5.3.  At this point none of the code in the previous
// section is executable because all the iCrap hasn't been defined yet.

// Note that there is an errro here in the text: it describes a type
// "iseqRep" which isn't mentioned in the code.

INil = Constructor("INil", [])
IStr = Constructor("IStr", ["String"])
IAppend = Constructor("IAppend", ["Seq1", "Seq2"])

iNil = INil()
iStr = IStr
function iIndent(seq) { return seq }
iNewline = IStr("\n")
iAppend = IAppend
function iDisplay(seq) { return flatten([seq]) }

// This is iterative rather than recursive because JavaScript
// implementations don't deal well with deep recursion.
function flatten(iseqlist) {
    var rv = ""
    while (iseqlist.length > 0) {
        var front = iseqlist.shift()
        if (front.tagname == 'INil') continue
        if (front.tagname == 'IStr') { rv = rv + front.String; continue }
        if (front.tagname == 'IAppend') {
            iseqlist.unshift(front.Seq2)
            iseqlist.unshift(front.Seq1)
            continue
        }
    }
    return rv
}

// Section 1.5.4

IIndent = Constructor("IIndent", ["Contents"])
INewline = Constructor("INewline", [])
iIndent = IIndent
iNewline = INewline()

function iDisplay(seq) { return flatten(0, [[seq, 0]]) }
function flatten(column, iseqlist) {
    var rv = ""
    while (iseqlist.length > 0) {
        var front = iseqlist.shift()
        var indent = front[1]
        // the cascading-if version of this was 2 lines longer
        case_analysis(front[0], {
            on_INil: function() { }, // exercise 1.6
            on_IStr: function(string) {  // exercise 1.6
                rv += string; column += string.length
            },
            on_IAppend: function(seq1, seq2) { // exercise 1.6
                iseqlist.unshift([seq2, indent])
                iseqlist.unshift([seq1, indent])
            },
            on_INewline: function() {
                rv += '\n' + repeat(indent, ' ').join('')
                column = indent  // one of the very few ways JS beats Python
            },
            on_IIndent: function(contents) {
                iseqlist.unshift([contents, column])
            },
        })
    }
    return rv
}

// exercises 1.7, 1.8: not done because not necessary

// section 1.5.6
iNum = iStr
function iFWNum(width, n) {
    var digits = n.toString()
    return iStr(repeat(width - digits.length, ' ').join('') + digits)
}

function iLayn(seqs) { // I lay in sex!
    var rv = []
    for (var ii = 0; ii < seqs.length; ii++) {
        var seq = seqs[ii]
        var n = ii + 1
        rv.append(iConcat([iFWNum(4, n), iStr(") "), iIndent(seq), iNewline]))
    }
    return iConcat(rv)
}

</script>
</head>
<body>
<pre>
<script type="text/javascript">
xpy = EAp(EAp(EVar("+"), EVar("x")), EVar("y"))
;(pprExpr_section_1_5_1(xpy) + "\n")
f5 = mkMultiAp(5, EVar("f"), EVar("x"))
;(pprExpr_section_1_5_1(f5) + "\n")
;(iDisplay(pprExpr(f5)) + "\n")
;(iDisplay(pprExpr(ELet(true, 
[["x", ENum(3)],
 ["y", EAp(EAp(EVar("+"), EVar("x")), ENum("4"))]],
EVar("y")
))))
</script>
</pre>
<h1>The core language</h1>

In 1991, Simon Peyton Jones and David R. Lester wrote "Implementing
Functional Languages: A Tutorial", in which they implement a simple
functional language (suitable for use as the intermediate language in
a functional language compiler) as an interpreter.  They call this
language "the core language".  This page contains a JavaScript
transliteration of their code, which is in Miranda.  So far I've
finished everything up to section 1.6.  Use Jesse Ruderman's
JavaScript Shell bookmarklet to access it, or the crappy JavaScript
line below:

<pre id="results"></pre>
<form onsubmit="var toprint; try { toprint = repr(eval(this.js.value)) } 
catch(e) { toprint = 'Exception: ' + e }; 
document.getElementById('results').appendChild(document.createTextNode(toprint 
+ '\n')); return false">
<input size="80" name="js" /><input type="submit" />
</form>

</body></html>

Reply via email to