5
\$\begingroup\$

I wrote an interpreter for Scheme which includes stop and copy garbage collection.

DefInt A-Z
DECLARE FUNCTION hash (s$)
DECLARE FUNCTION READOBJ (depth)
DECLARE FUNCTION READTOKEN (depth)
DECLARE FUNCTION STRTOATOM (s$)
DECLARE FUNCTION CONS (car, cdr)
DECLARE FUNCTION READLIST (depth)
DECLARE FUNCTION ALLOC ()
DECLARE SUB PRINTOBJ (id)
DECLARE FUNCTION EVALOBJ (id, env)
DECLARE FUNCTION apply (f, args)
DECLARE FUNCTION lookup (anum, env)
DECLARE FUNCTION lvals (id, env)
DECLARE SUB defvar (var, vals, env)
DECLARE SUB setvar (id, vals, env)
DECLARE FUNCTION mkprimop (id)
DECLARE FUNCTION collect(p)
DECLARE SUB gc(root)


' Make these smaller to get it to work in QBASIC / QuickBASIC
' Compiles in QB64 or FreeBASIC
Const msize = 16384 'size of memory -- arbitrary
Const hsize = 16384 'size of hash table -- should be power of 2

Dim Shared bufpos As Integer ' position in input line
Dim Shared buf As String ' line of input from STDIN
Dim Shared hptr ' next location in array below
Dim Shared atom$(0 To hsize - 1) ' hash table for interning strings
Dim Shared heap(2 * msize - 1, 2) ' store LISP objects
Dim Shared mmin, nmin, gcnow ' used by garbage collector

mmin = 1: nmin = msize 
' split memory storage in 2 for garbage collector
' on GC, all objects are compacted into the other half
' and mmin, nmin switch roles

Const TRUE = -1 ' all ones bit pattern (conventional in BASIC)
Const FALSE = 0

' Constants for object type
Const TNIL = 0
Const TCONS = 2
Const TNUM = 3
Const TSYM = 4
Const TPROC = 5
Const TPPROC = 6

' Constants for token type
Const TOKNIL = 0
Const TOKERR = -1
Const TOKOPEN = -2
Const TOKCLOSE = -3
Const TOKQUOTE = -4
Const TOKDOT = -5

' Constants for primitive functions
Const PPLUS = 1
Const PMINUS = 2
Const PTIMES = 3
Const PCONS = 4
Const PCAR = 5
Const PCDR = 6
Const PEQUAL = 7
Const PNOT = 8
Const PEQ = 9
Const PSETCAR = 10
Const PSETCDR = 11
Const PAPPLY = 12
Const PLIST = 13
Const PREAD = 14
Const PLT = 15
Const PGT = 16
Const PGEQ = 17
Const PLEQ = 18
Const PNUMP = 20
Const PPROCP = 21
Const PSYMP = 22
Const PCONSP = 24

hptr = mmin: bufpos = 1

' create an empty environment frame
vars = TNIL
vals = TNIL
frame = CONS(vars, vals)
env = CONS(frame, TNIL)

' add primitive functions to environment
Call defvar(STRTOATOM("+"), mkprimop(PPLUS), env)
Call defvar(STRTOATOM("-"), mkprimop(PMINUS), env)
Call defvar(STRTOATOM("*"), mkprimop(PTIMES), env)
Call defvar(STRTOATOM("CONS"), mkprimop(PCONS), env)
Call defvar(STRTOATOM("CAR"), mkprimop(PCAR), env)
Call defvar(STRTOATOM("CDR"), mkprimop(PCDR), env)
Call defvar(STRTOATOM("="), mkprimop(PEQUAL), env)

Call defvar(STRTOATOM("NOT"), mkprimop(PNOT), env)
Call defvar(STRTOATOM("EQ?"), mkprimop(PEQ), env)
Call defvar(STRTOATOM("EQV?"), mkprimop(PEQ), env)
Call defvar(STRTOATOM("T"), STRTOATOM("T"), env) ' true
Call defvar(STRTOATOM("SET-CAR!"), mkprimop(PSETCAR), env)
Call defvar(STRTOATOM("SET-CDR!"), mkprimop(PSETCDR), env)
Call defvar(STRTOATOM("APPLY"), mkprimop(PAPPLY), env)
Call defvar(STRTOATOM("LIST"), mkprimop(PLIST), env)
Call defvar(STRTOATOM("READ"), mkprimop(PREAD), env)

Call defvar(STRTOATOM("<"), mkprimop(PLT), env)
Call defvar(STRTOATOM(">"), mkprimop(PGT), env)
Call defvar(STRTOATOM(">="), mkprimop(PGEQ), env)
Call defvar(STRTOATOM("<="), mkprimop(LEQ), env)


Call defvar(STRTOATOM("SYMBOL?"), mkprimop(PSYMP), env)
Call defvar(STRTOATOM("NUMBER?"), mkprimop(PNUMP), env)
Call defvar(STRTOATOM("PROCEDURE?"), mkprimop(PPROCP), env)
Call defvar(STRTOATOM("PAIR?"), mkprimop(PCONSP), env)

' Read eval print loop
Do
    s = READOBJ(0) ' read a LISP object
    Select Case s ' check the return value
        Case TOKCLOSE ' ignore extra close parens 
            ' unmatched closed parenthesis
        Case TOKDOT
            Print "dot used outside list"
        Case TOKERR
            Print "[Error]"
        Case Else ' no syntax error, evaluate and print
            Call PRINTOBJ(EVALOBJ(s, env))
    End Select
    Print
    If gcnow Then Call gc(env) ' need to garbage collect
Loop

' return the index of a new LISP cell
Function ALLOC
    ALLOC = hptr
    hptr = hptr + 1
    If hptr > (mmin + 3 * (msize / 4)) Then gcnow = True
End Function

' apply the function in the lisp cell with index id
' to the arguments in args (also an index of a lisp cell)
Function apply (id, args)
    If heap(id, 0) = TPROC Then ' user-defined procedure
' stored as a LISP list
        params = heap(id, 1) ' car is params
        body = heap(heap(id, 2), 1) ' cadr is body definition
        procenv = heap(heap(id, 2), 2) ' cddr is environment
' add the params to the environment
        env = CONS(CONS(params, args), procenv)
        Do While heap(body, 2) 
' if body contains more than one expression, evaluate in sequence
' and then take the last one
            t = heap(body, 1)
            t = EVALOBJ(t, env) 'ignore result
            body = heap(body, 2)
        Loop
        t = heap(body, 1)
        apply = EVALOBJ(t, env)
    ElseIf heap(id, 0) = TPPROC Then ' primitive (built-in) procedure
        Select Case heap(id, 1) 
' long switch statement for each builtin
            Case PPLUS
                sum = 0
                a = args
                While a
                    sum = sum + heap(heap(a, 1), 1)
                    a = heap(a, 2)
                Wend
                p = ALLOC
                heap(p, 0) = TNUM
                heap(p, 1) = sum
                apply = p
            Case PTIMES
                prod = 1
                a = args
                While a
                    prod = prod * heap(heap(a, 1), 1)
                    a = heap(a, 2)
                Wend
                p = ALLOC
                heap(p, 0) = TNUM
                heap(p, 1) = prod
                apply = p
            Case PCONS
                apply = CONS(heap(args, 1), heap(heap(args, 2), 1))
            Case PCAR
                apply = heap(heap(args, 1), 1)
            Case PCDR
                apply = heap(heap(args, 1), 2)
            Case PEQUAL
                If args = TNIL Then apply = STRTOATOM("T"): Exit Function
                f = heap(heap(args, 1), 1)
                a = heap(args, 2)
                Do While a
                    If heap(heap(a, 1), 1) <> f Then apply = TNIL: Exit Function
                    a = heap(a, 2)
                Loop
                apply = STRTOATOM("T"): Exit Function
            Case PNOT
                If heap(args, 1) Then apply = TNIL Else apply = STRTOATOM("T")
            Case PEQ
                arg1 = heap(args, 1)
                arg2 = heap(heap(args, 2), 1)
                If heap(arg1, 0) <> heap(arg2, 0) Then apply = TNIL: Exit Function
                Select Case heap(arg1, 0)
                    Case TNUM, TPROC, TPPROC, TSYM
                        If heap(arg1, 1) = heap(arg2, 1) Then apply = STRTOATOM("T")
                    Case TCONS, TNIL
                        If arg1 = arg2 Then apply = STRTOATOM("T")
                End Select
            Case PLT
                If args = TNIL Then apply = STRTOATOM("T"): Exit Function
                f = heap(heap(args, 1), 1)
                a = heap(args, 2)
                Do While a
                    If f < heap(heap(a, 1), 1) Then
                        f = heap(heap(a, 1), 1)
                        a = heap(a, 2)
                    Else
                        apply = TNIL: Exit Function
                    End If
                Loop
                apply = STRTOATOM("T"): Exit Function
            Case PGT
                If args = TNIL Then apply = STRTOATOM("T"): Exit Function
                f = heap(heap(args, 1), 1)
                a = heap(args, 2)
                Do While a
                    If f > heap(heap(a, 1), 1) Then
                        f = heap(heap(a, 1), 1)
                        a = heap(a, 2)
                    Else
                        apply = TNIL: Exit Function
                    End If
                Loop
                apply = STRTOATOM("T"): Exit Function
            Case PLEQ
                If args = TNIL Then apply = STRTOATOM("T"): Exit Function
                f = heap(heap(args, 1), 1)
                a = heap(args, 2)
                Do While a
                    If f <= heap(heap(a, 1), 1) Then
                        f = heap(heap(a, 1), 1)
                        a = heap(a, 2)
                    Else
                        apply = TNIL: Exit Function
                    End If
                Loop
                apply = STRTOATOM("T"): Exit Function
            Case PGEQ
                If args = TNIL Then apply = STRTOATOM("T"): Exit Function
                f = heap(heap(args, 1), 1)
                a = heap(args, 2)
                Do While a
                    If f >= heap(heap(a, 1), 1) Then
                        f = heap(heap(a, 1), 1)
                        a = heap(a, 2)
                    Else
                        apply = TNIL: Exit Function
                    End If
                Loop
                apply = STRTOATOM("T"): Exit Function
            Case PSETCAR
                arg1 = heap(args, 1)
                arg2 = heap(heap(args, 2), 1)
                heap(arg1, 1) = arg2
            Case PSETCDR
                arg1 = heap(args, 1)
                arg2 = heap(heap(args, 2), 1)
                heap(arg2, 2) = arg2
            Case PAPPLY
                arg1 = heap(args, 1)
                arg2 = heap(heap(args, 2), 1)
                apply = apply(arg1, arg2)
            Case PLIST
                apply = args
            Case PREAD
                apply = READOBJ(0)
            Case PMINUS
                arg1 = heap(heap(args, 1), 1)
                rargs = heap(args, 2)
                If rargs Then
                    res = arg1
                    While rargs
                        res = res - heap(heap(rargs, 1), 1)
                        rargs = heap(rargs, 2)
                    Wend
                    p = ALLOC
                    heap(p, 0) = TNUM: heap(p, 1) = res: apply = p
                Else
                    p = ALLOC: heap(p, 0) = TNUM: heap(p, 1) = -arg1
                    apply = p
                End If
            Case PSYMP
                targ1 = heap(heap(args, 1), 0)
                If targ1 = TSYM Then apply = STRTOATOM("T")
            Case PNUMP
                targ1 = heap(heap(args, 1), 0)
                If targ1 = TNUM Then apply = STRTOATOM("T")
            Case PPROCP
                targ1 = heap(heap(args, 1), 0)
                If targ1 = TPROC Or targ1 = TPPROC Then apply = STRTOATOM("T")
            Case PCONSP
                targ1 = heap(heap(args, 1), 0)
                If targ1 = TCONS Then apply = STRTOATOM("T")
        End Select
    Else
        Print "Bad application -- not a function"
        apply = TOKERR
    End If
End Function

Function CONS (car, cdr)
    p = ALLOC
    heap(p, 0) = TCONS
    heap(p, 1) = car
    heap(p, 2) = cdr
    CONS = p
End Function

Sub defvar (id, value, env)
    anum = heap(id, 1)
    frame = heap(env, 1)
    vars = heap(frame, 1)
    vals = heap(frame, 2)
    While vars
        If heap(heap(vars, 1), 1) = anum Then
            heap(vals, 1) = value: Exit Sub
        End If
        vars = heap(vars, 2): vals = heap(vals, 2)
    Wend
    vars = heap(frame, 1)
    vals = heap(frame, 2)
    heap(frame, 1) = CONS(id, vars)
    heap(frame, 2) = CONS(value, vals)
End Sub

Function EVALOBJ (id, env)
    1 Select Case heap(id, 0)
        Case TNIL, TNUM ' self-evaluating
            EVALOBJ = id
        Case TSYM
            EVALOBJ = lookup(heap(id, 1), env)
        Case TCONS
            o = heap(id, 1)
            t = heap(o, 0)
            If t = TSYM Then
                a$ = atom$(heap(o, 1)) ' symbol name of car(id)
                Select Case a$
                    Case "QUOTE"
                        EVALOBJ = heap(heap(id, 2), 1)
                    Case "SET!"
                        vid = heap(heap(id, 2), 1) 'cadr
                        aval = heap(heap(heap(id, 2), 2), 1) 'caddr
                        Call setvar(vid, EVALOBJ(aval, env), env)
                    Case "DEFINE"
                        vid = heap(heap(id, 2), 1)
                        aval = heap(heap(heap(id, 2), 2), 1)
                        Call setvar(vid, EVALOBJ(aval, env), env)
                    Case "IF"
                        ' (if pred ic ia)
                        pred = heap(heap(id, 2), 1) 'predicate = cadr
                        ic = heap(heap(heap(id, 2), 2), 1) ' caddr
                        ia = heap(heap(heap(heap(id, 2), 2), 2), 1) ' cadddr
                        If EVALOBJ(pred, env) Then
                            ' return EVALOBJ(ic,env)
                            id = ic: GoTo 1
                        Else
                            ' return EVALOBJ(ia,env)
                            id = ia: GoTo 1
                        End If
                    Case "LAMBDA"
                        p = ALLOC
                        heap(p, 0) = TPROC
                        heap(p, 1) = heap(heap(id, 2), 1) ' cadr = args
                        heap(p, 2) = CONS(heap(heap(id, 2), 2), env) 'caddr = body
                        EVALOBJ = p
                    Case "BEGIN"
                        seq = heap(id, 2)
                        Do While heap(seq, 2)
                            t = heap(seq, 1)
                            t = EVALOBJ(t, env) 'ignore result
                            seq = heap(seq, 2)
                        Loop
                        id = heap(seq, 1): GoTo 1
                    Case "AND"
                        seq = heap(id, 2)
                        Do While heap(seq, 2)
                            t = heap(seq, 1)
                            t = EVALOBJ(t, env)
                            If t = 0 Then EVALOBJ = 0: Exit Function
                            seq = heap(seq, 2)
                        Loop
                        id = heap(seq, 1): GoTo 1
                    Case "OR"
                        seq = heap(id, 2)
                        Do While heap(seq, 2)
                            t = heap(seq, 1)
                            t = EVALOBJ(t, env)
                            If t Then EVALOBJ = t: Exit Function
                            seq = heap(seq, 2)
                        Loop
                        id = heap(seq, 1): GoTo 1
                    Case "COND"
                        clauses = heap(id, 2)
                        While clauses
                            clause = heap(clauses, 1)
                            pred = heap(clause, 1)
                            If EVALOBJ(pred, env) Then
                                seq = heap(clause, 2)
                                Do While heap(seq, 2)
                                    t = heap(seq, 1)
                                    t = EVALOBJ(t, env) 'ignore result
                                    seq = heap(seq, 2)
                                Loop
                                id = heap(seq, 1): GoTo 1
                            End If
                            clauses = heap(clauses, 2)
                        Wend
                    Case Else
                        args = heap(id, 2)
                        proc = EVALOBJ(o, env)
                        EVALOBJ = apply(proc, lvals(args, env))
                End Select
            Else
                args = heap(id, 2)
                proc = EVALOBJ(o, env)
                EVALOBJ = apply(proc, lvals(args, env))
            End If
        Case Else
            Print "Unhandled expression type: "; a$
            EVALOBJ = id
    End Select
End Function

Function hash (s$)
    Dim h As Long
    For i = 1 To Len(s$)
        c = Asc(Mid$(s$, i, 1))
        h = (h * 33 + c) Mod hsize
    Next
    hash = h
End Function

Function lookup (anum, env)
    ' env is a list of (vars . vals) frames
    ' where: vars is a list of symbols
    '        vals is a list of their values
    e = env
    Do
        frame = heap(e, 1) ' get the first frame

        vars = heap(frame, 1) ' vars is car

        vals = heap(frame, 2) ' vals is cdr

        While vars ' while vars left to check
            If heap(heap(vars, 1), 1) = anum Then 'atom number of car(vars) = anum
                lookup = heap(vals, 1) ' car(vals)
                Exit Function
            End If
            vars = heap(vars, 2) 'cdr(vars)
            vals = heap(vals, 2) 'cdr(vals)
        Wend
        e = heap(e, 2) ' cdr(e)
    Loop While e
    Print "Unbound variable: "; atom$(anum): lookup = TOKERR
End Function

Function lvals (id, env)
    If heap(id, 0) = TCONS Then
        car = heap(id, 1)
        ecar = EVALOBJ(car, env)
        head = CONS(ecar, 0)
        l = heap(id, 2): prev = head
        While l
            car = heap(l, 1)
            ecar = EVALOBJ(car, env)
            new = CONS(ecar, 0)
            heap(prev, 2) = new
            prev = new
            l = heap(l, 2)
        Wend
        lvals = head
    Else
        lvals = 0
    End If
End Function

Function mkprimop (id)
    p = ALLOC
    heap(p, 0) = TPPROC
    heap(p, 1) = id
    mkprimop = p
End Function

Sub PRINTOBJ (id)

    If id = TOKERR Then Print "[Error]": Exit Sub
    Select Case heap(id, 0)
        Case TNIL
            Print "()";
        Case TCONS
            Print "(";
            printlist:
            Call PRINTOBJ(heap(id, 1))
            Print " ";
            cdr = heap(id, 2)
            If heap(cdr, 0) = TCONS Then id = cdr: GoTo printlist
            If heap(cdr, 0) = TNIL Then
                Print ")";
            Else
                Print ".";
                Call PRINTOBJ(cdr)
                Print ")";
            End If
        Case TNUM
            Print Str$(heap(id, 1));
        Case TSYM
            Print atom$(heap(id, 1));
        Case TPROC, TPPROC
            Print "[Procedure]"
    End Select
End Sub

Function READLIST (depth)
    SH = READOBJ(depth)
    Select Case SH
        Case TOKERR
            READLIST = TOKERR
        Case TOKCLOSE
            READLIST = 0
        Case TOKDOT
            SH = READOBJ(depth)
            Select Case SH
                Case TOKERR, TOKDOT, TOKCLOSE
                    READLIST = TOKERR
                Case Else
                    ST = READLIST(depth)
                    If ST Then READLIST = TOKERR Else READLIST = SH
            End Select
        Case Else
            ST = READLIST(depth)
            If ST = TOKERR Then READLIST = TOKERR Else READLIST = CONS(SH, ST)
    End Select
End Function

Function READOBJ (depth)
    tok = READTOKEN(depth)
    Select Case tok
        Case TOKOPEN
            s = READLIST(depth + 1)
            READOBJ = s
        Case TOKQUOTE
            tok = READOBJ(depth + 1)
            Select Case tok
                Case TOKCLOSE
                    Print "warning: quote before close parenthesis"
                    READOBJ = tok
                Case TOKDOT
                    Print "warning: quote before dot"
                    READOBJ = tok
                Case Else
                    s = CONS(STRTOATOM("QUOTE"), CONS(tok, 0))
                    READOBJ = s
            End Select
        Case Else
            READOBJ = tok
    End Select
End Function

Function READTOKEN (depth)
    
    start1: bufend = Len(buf)
    While bufpos < bufend And InStr(" " + Chr$(9), Mid$(buf, bufpos, 1))
        bufpos = bufpos + 1
    Wend
    c$ = Mid$(buf, bufpos, 1)
    If InStr(":;", c$) Then
        If c$ = ":" Then
            bufpos = bufpos + 1
            If bufpos <= bufend Then
                Select Case Mid$(buf, bufpos, 1)
                    Case "q", "Q" ' quit
                        System
                    Case "g", "G" ' garbage collect now
                        gcnow = -1
                    Case Else
                        READTOKEN = TOKERR
                        Exit Function
                End Select
            End If
        End If
        bufpos = bufend + 1
    End If
    If bufpos > bufend Then
        If depth = 0 Then Print "]=> ";
        Line Input buf
        bufend = Len(buf)
        bufpos = 1
        GoTo start1
    End If
    Select Case c$
        Case "("
            bufpos = bufpos + 1
            READTOKEN = TOKOPEN
        Case ")"
            bufpos = bufpos + 1
            READTOKEN = TOKCLOSE
        Case "'"
            bufpos = bufpos + 1
            READTOKEN = TOKQUOTE
        Case "."
            bufpos = bufpos + 1
            READTOKEN = TOKDOT
        Case Else
            strbeg = bufpos
            bufpos = bufpos + 1
            Do While bufpos <= bufend
                c$ = Mid$(buf, bufpos, 1)
                If c$ = " " Or c$ = "." Or c$ = "(" Or c$ = ")" Then Exit Do
                bufpos = bufpos + 1
            Loop
            READTOKEN = STRTOATOM(Mid$(buf, strbeg, bufpos - strbeg))
    End Select
End Function

Sub setvar (id, value, env)
    anum = heap(id, 1)
    e = env
    Do
        frame = heap(e, 1)
        vars = heap(frame, 1)
        vals = heap(frame, 2)
        While vars
            If heap(heap(vars, 1), 1) = anum Then
                heap(vals, 1) = value: Exit Sub
            End If
            vars = heap(vars, 2): vals = heap(vals, 2)
        Wend
        e = heap(e, 2)
    Loop While e
    Call defvar(id, value, env)
End Sub

Function STRTOATOM (s$)
    l = Len(s$)
    c$ = Left$(s$, 1)
    If (c$ = "-" And l >= 2) Or (c$ >= "0" And c$ <= "9") Then
        v = 0
        If c$ = "-" Then neg = 1: idx = 2 Else neg = 0: idx = 1
        For idx = idx To l
            c$ = Mid$(s$, idx, 1)
            If (c$ >= "0" And c$ <= "9") Then
                v = v * 10 + (Asc(c$) - Asc("0"))
            Else
                Exit For
            End If
        Next
        If idx = l + 1 Then
            If neg Then v = -v
            p = ALLOC
            heap(p, 0) = TNUM
            heap(p, 1) = v
            STRTOATOM = p: Exit Function
        End If
    End If
    If UCase$(s$) = "NIL" Then STRTOATOM = TOKNIL: Exit Function

    i = hash(UCase$(s$))
    For count = 1 To hsize
        If atom$(i) = UCase$(s$) Then
            found = true: Exit For
        ElseIf atom$(i) = "" Then
            atom$(i) = UCase$(s$)
            found = true
            Exit For
        Else
            i = (i + count) Mod hsize
        End If
    Next
    If Not found Then Print "Symbol table full!"
    p = ALLOC: heap(p, 0) = TSYM: heap(p, 1) = i
    STRTOATOM = p
End Function

' stop and copy garbage collection
Sub gc (root)

    hptr = nmin

' copy root pointer
    scan = hptr
    x = ALLOC
    For i = 0 To 2
        heap(x, 1) = heap(root, i)
    Next
    heap(root, 0) = -1
    heap(root, 1) = x
    root = x

' while new things have been copied
    While scan <> hptr
        If heap(scan, 0) = TCONS Or heap(scan, 0) = TPROC Then
            heap(scan, 1) = collect(heap(scan, 1))
            heap(scan, 2) = collect(heap(scan, 2))
        End If
        scan = scan + 1
    Wend

' swap pointers to two halves of memory
    Swap mmin, nmin
    Swap mmax, nmax
    gcnow = False
End Sub

' return new location of lisp cell pointed to at p 
Function collect (p)

    Select Case heap(p, 0)

        Case -1
            collect = heap(p, 1)

        Case TNIL
            collect = 0

        Case Else
            x = ALLOC

            ' copy the entire structure
            For i = 0 To 2
                heap(x, i) = heap(p, i)
            Next

            ' write forwarding address
            heap(p, 0) = -1
            heap(p, 1) = x
            collect = x
    End Select

End Function

https://gist.github.com/menezesd/2a2cedb7fea564c4de99889f2fd78e92

\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.