glycerine / zygomys

Zygo is a Lisp interpreter written in 100% Go. Central use case: dynamically compose Go struct trees in a zygo script, then invoke compiled Go functions on those trees. Makes Go reflection easy.
https://github.com/glycerine/zygomys/wiki
BSD 2-Clause "Simplified" License
1.71k stars 81 forks source link

While loop #50

Open japanoise opened 4 years ago

japanoise commented 4 years ago

It'd be rad to have a while loop construct. I've had to hack it in myself, using this:

func truthyResult(env *zygo.Zlisp, fun *zygo.SexpFunction) (bool, error) {
    res, err := env.Apply(fun, []zygo.Sexp{})
    if err != nil {
        return false, err
    }
    switch rt := res.(type) {
    case *zygo.SexpBool:
        return rt.Val, nil
    case *zygo.SexpSentinel:
        return false, nil
    default:
        return true, nil
    }
}

// While hacks a while loop into zygomys
func While(env *zygo.Zlisp, name string, args []zygo.Sexp) (zygo.Sexp, error) {
    if len(args) != 2 {
        return zygo.SexpNull, errors.New("Syntax: (while cond body)")
    }
    var cond, body *zygo.SexpFunction
    switch st := args[0].(type) {
    case *zygo.SexpFunction:
        cond = st
    default:
        return zygo.SexpNull, errors.New("While loop takes two functions")
    }
    switch st := args[1].(type) {
    case *zygo.SexpFunction:
        body = st
    default:
        return zygo.SexpNull, errors.New("While loop takes two functions")
    }
    result, err := truthyResult(env, cond)
    if err != nil {
        return zygo.SexpNull, err
    }
    for result {
        _, err := env.Apply(body, []zygo.Sexp{})
        result, err = truthyResult(env, cond)
        if err != nil {
            return zygo.SexpNull, err
        }
    }
    return zygo.SexpNull, err
}

Is there a better way? Or would this require extending the language?

I'd like to be able to do:


(while (== true cond)
  (some)
  (body))
glycerine commented 4 years ago

Hi @japanoise that would be pretty cool. Just add a couple of correctness tests to the test suite and file a pull request, and we'll put it in.

japanoise commented 4 years ago

Well, currently the API is too clumsy. I wouldn't put it in as-is.

You have to do e.g.

(while (fn [] (==cond true) (fn [] (body goes here))

I was wondering if you had a macro hack or something to make it nicer.

glycerine commented 4 years ago

The proper way would be to add it to the built in codegen, alongside the for builtin. You could probably just copy and modify the for Generator.GenerateForLoop code in generator.go:817 to be like Go and only need the middle test.