gnolang / bounties

cosmos/gno ecosystem bounties
9 stars 8 forks source link

Complete bounty #4 #34

Closed grepsuzette closed 9 months ago

grepsuzette commented 1 year ago

Added an example to my port of joeson. It's a small lisp vm, it has an REPL.

As I understand you don't necessarily have the time to jump into the code right now I'll provide a small overview here.

The main_test.go is mostly:

package main

import (
    "fmt"
    "testing"
)

type exprExpectation struct {
    expr        string
    expectation string
}

func duo(s string, s2 string) exprExpectation {
    // return exprExpectation{strings.Replace(s, "\n", "", -1), s2}
    return exprExpectation{s, s2}
}

const (
    TRUE  = "1.000000"
    FALSE = "0.000000"
)

var tests = []exprExpectation{
    // ------------ user-defined  -----------------------------
    duo("(define (double x) (+ x x))", "()"),
    duo("(double 15)", "30.000000"),
    duo("(define (quadruple x) (double (double x)))", "()"),
    duo("(quadruple 15)", "60.000000"),
    duo("(define (cadr l) (car (cdr l)))", "()"),
    duo(`(cadr ("a" "b" "c"))`, `"b"`),
    duo("(define (caddr l) (car (cdr (cdr l))))", "()"),
    duo(`(caddr ("a" "b" "c"))`, `"c"`),

    // ------------ alternation -------------
    duo(`(if (== 4 4) "ok" "nok")`, `"ok"`),
    duo(`(if (!= 4 4) "ok" "nok")`, `"nok"`),
    duo(`(define (codeSize l)
         (cond
           ((>= l 9) "L")
           ((>= l 5) "M")
           (else "S")
         ))`, `()`),
    duo(`(codeSize 10)`, `"L"`),
    duo(`(codeSize 6)`, `"M"`),
    duo(`(codeSize 2)`, `"S"`),

    // ------------------------
    duo(`(define (divisible? n m) (== (% n m) 0))`, `()`),
    duo(`(divisible? 1000000 10)`, TRUE),
    duo(`(divisible? 1000000 7)`, FALSE),
    duo(`(define (fact n) (if (<= n 0) 1 (* n (fact (- n 1)))))`, `()`),
    duo(`(fact 0)`, `1.000000`),
    duo(`(fact 4)`, `24.000000`),
    duo(`(fact 5)`, `120.000000`),
}

func Test(t *testing.T) {
    gm := grammar()
    m := NewMachine()
    // simply compare m.Eval(gm.ParseString(`k`)) with `v`.
    for _, o := range tests {
        k := o.expr
        v := o.expectation
        t.Run(fmt.Sprintf("eval %s expected to give %s", k, v), func(t *testing.T) {
            if ast, e := gm.ParseString(k); e == nil {
                s := stripansi(m.Eval(ast.(Expr)).ContentString())
                if s != v {
                    t.Errorf("%s expected to eval as %s gave %s instead\n", k, v, s)
                }
            } else {
                t.Errorf("%s expect to eval as %s did not even parse! error = %s\n", k, v, e)
            }
        })
    }
}

Grammar is very small, as in lisp everything is list:

import (
    j "github.com/grepsuzette/joeson"
)

// -- The parsing grammar

func i(a ...any) j.ILine                                 { return j.I(a...) }
func o(a ...any) j.OLine                                 { return j.O(a...) }
func named(name string, lineStringOrAst any) j.NamedRule { return j.Named(name, lineStringOrAst) }
func rules(a ...j.Line) []j.Line                         { return a }

var grammarRules = rules(
    o(named("toplevelexpr", "_ expr:expr _"), parseTopLevelExpr),
    i(named("expr", "l:list | s:string | n:number | operator:operator"), parseExpr),
    i(named("list", "'(' _ (expr*__) _ ')'"), parseList),
    i(named("operator", "word | '+' | '-' | '*' | '/' | '%' | '>=' | '<=' | '!=' | '=='| '<' | '>' |  '=' "), parseOperator),
    i(named("_", "(' ' | '\t' | '\n')*")),
    i(named("__", "(' ' | '\t' | '\n')+")),
    i(named("string", "'\"' s:([^\"]*) '\"'"), parseString),
    i(named("word", "/[a-zA-Z\\._][a-zA-Z\\._0-9?]*/")),
    i(named("number", "/-?[0-9]+/") /* TODO non-int */, func(it j.Ast) j.Ast { return j.NewNativeIntFrom(it) }),
    i(named(".", "/[\\s\\S]/")),
    i(named("ESC1", "'\\\\' .")),
)

This was a week-end experimentation that took a couple of days more. Lisp grammar is too simple for demonstration purposes, so I think I may make another example one day. Hopefully soon, but if I have proof somebody reads this it will accelerate things :p

Linking to #33