Enforce blocks in control forms to be lambdas. Make except-var optional.

This commit is contained in:
Qi Xiao 2017-02-18 05:25:42 +00:00
parent 28fe942cad
commit 59d2bd580c
3 changed files with 66 additions and 51 deletions

View File

@ -16,12 +16,17 @@ func (aw *argsWalker) more() bool {
return aw.idx < len(aw.form.Args)
}
func (aw *argsWalker) next() *parse.Compound {
func (aw *argsWalker) peek() *parse.Compound {
if !aw.more() {
aw.cp.errorpf(aw.form.End(), aw.form.End(), "need more arguments")
}
return aw.form.Args[aw.idx]
}
func (aw *argsWalker) next() *parse.Compound {
n := aw.peek()
aw.idx++
return aw.form.Args[aw.idx-1]
return n
}
// nextIs returns whether the next argument's source matches the given text. It
@ -34,9 +39,26 @@ func (aw *argsWalker) nextIs(text string) bool {
return false
}
func (aw *argsWalker) nextLedBy(leader string) *parse.Compound {
// nextMustLambda fetches the next argument, raising an error if it is not a
// lambda.
func (aw *argsWalker) nextMustLambda() *parse.Primary {
n := aw.next()
if len(n.Indexings) != 1 {
aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
}
if len(n.Indexings[0].Indicies) != 0 {
aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
}
pn := n.Indexings[0].Head
if pn.Type != parse.Lambda {
aw.cp.errorpf(n.Begin(), n.End(), "must be lambda")
}
return pn
}
func (aw *argsWalker) nextMustLambdaIfAfter(leader string) *parse.Primary {
if aw.nextIs(leader) {
return aw.next()
return aw.nextMustLambda()
}
return nil
}

View File

@ -226,30 +226,31 @@ func use(ec *EvalCtx, modname string, pfilename *string) {
func compileIf(cp *compiler, fn *parse.Form) OpFunc {
args := cp.walkArgs(fn)
var condNodes, bodyNodes []*parse.Compound
var condNodes []*parse.Compound
var bodyNodes []*parse.Primary
for {
condNodes = append(condNodes, args.next())
bodyNodes = append(bodyNodes, args.next())
bodyNodes = append(bodyNodes, args.nextMustLambda())
if !args.nextIs("elif") {
break
}
}
elseNode := args.nextLedBy("else")
elseNode := args.nextMustLambdaIfAfter("else")
args.mustEnd()
condOps := cp.compoundOps(condNodes)
bodyOps := cp.compoundOps(bodyNodes)
bodyOps := cp.primaryOps(bodyNodes)
var elseOp ValuesOp
if elseNode != nil {
elseOp = cp.compoundOp(elseNode)
elseOp = cp.primaryOp(elseNode)
}
return func(ec *EvalCtx) {
bodies := make([]Callable, len(bodyOps))
for i, bodyOp := range bodyOps {
bodies[i] = bodyOp.execMustOneFn(ec)
bodies[i] = bodyOp.execlambdaOp(ec)
}
else_ := elseOp.execMustOneFn(ec)
else_ := elseOp.execlambdaOp(ec)
for i, condOp := range condOps {
if allTrue(condOp.Exec(ec)) {
bodies[i].Call(ec, NoArgs, NoOpts)
@ -265,14 +266,14 @@ func compileIf(cp *compiler, fn *parse.Form) OpFunc {
func compileWhile(cp *compiler, fn *parse.Form) OpFunc {
args := cp.walkArgs(fn)
condNode := args.next()
bodyNode := args.next()
bodyNode := args.nextMustLambda()
args.mustEnd()
condOp := cp.compoundOp(condNode)
bodyOp := cp.compoundOp(bodyNode)
bodyOp := cp.primaryOp(bodyNode)
return func(ec *EvalCtx) {
body := bodyOp.execMustOneFn(ec)
body := bodyOp.execlambdaOp(ec)
for {
cond := condOp.Exec(ec)
@ -298,8 +299,8 @@ func compileFor(cp *compiler, fn *parse.Form) OpFunc {
args := cp.walkArgs(fn)
varNode := args.next()
iterNode := args.next()
bodyNode := args.next()
elseNode := args.nextLedBy("else")
bodyNode := args.nextMustLambda()
elseNode := args.nextMustLambdaIfAfter("else")
args.mustEnd()
varOp, restOp := cp.lvaluesOp(varNode.Indexings[0])
@ -308,10 +309,10 @@ func compileFor(cp *compiler, fn *parse.Form) OpFunc {
}
iterOp := cp.compoundOp(iterNode)
bodyOp := cp.compoundOp(bodyNode)
bodyOp := cp.primaryOp(bodyNode)
var elseOp ValuesOp
if elseNode != nil {
elseOp = cp.compoundOp(elseNode)
elseOp = cp.primaryOp(elseNode)
}
return func(ec *EvalCtx) {
@ -330,8 +331,8 @@ func compileFor(cp *compiler, fn *parse.Form) OpFunc {
ec.errorpf(iterOp.Begin, iterOp.End, "should be one iterable")
}
body := bodyOp.execMustOneFn(ec)
elseBody := elseOp.execMustOneFn(ec)
body := bodyOp.execlambdaOp(ec)
elseBody := elseOp.execlambdaOp(ec)
iterated := false
iterable.Iterate(func(v Value) bool {
@ -360,48 +361,48 @@ func compileFor(cp *compiler, fn *parse.Form) OpFunc {
func compileTry(cp *compiler, fn *parse.Form) OpFunc {
Logger.Println("compiling try")
args := cp.walkArgs(fn)
bodyNode := args.next()
bodyNode := args.nextMustLambda()
Logger.Printf("body is %q", bodyNode.SourceText())
var exceptVarNode *parse.Indexing
var exceptNode *parse.Compound
var exceptNode *parse.Primary
if args.nextIs("except") {
Logger.Println("except-ing")
n := args.next()
if len(n.Indexings) != 1 {
cp.errorpf(n.Begin(), n.End(), "should be one variable")
n := args.peek()
// Is this a variable?
if len(n.Indexings) == 1 && n.Indexings[0].Head.Type == parse.Bareword {
exceptVarNode = n.Indexings[0]
args.next()
}
exceptVarNode = n.Indexings[0]
exceptNode = args.next()
Logger.Printf("except-var = %q, except = %q", exceptVarNode.SourceText(), exceptNode.SourceText())
exceptNode = args.nextMustLambda()
}
elseNode := args.nextLedBy("else")
finallyNode := args.nextLedBy("finally")
elseNode := args.nextMustLambdaIfAfter("else")
finallyNode := args.nextMustLambdaIfAfter("finally")
args.mustEnd()
var exceptVarOp LValuesOp
var bodyOp, exceptOp, elseOp, finallyOp ValuesOp
bodyOp = cp.compoundOp(bodyNode)
bodyOp = cp.primaryOp(bodyNode)
if exceptVarNode != nil {
var restOp LValuesOp
exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode)
if restOp.Func != nil {
cp.errorpf(restOp.Begin, restOp.End, "may not use @rest in except variable")
}
exceptOp = cp.compoundOp(exceptNode)
exceptOp = cp.primaryOp(exceptNode)
}
if elseNode != nil {
elseOp = cp.compoundOp(elseNode)
elseOp = cp.primaryOp(elseNode)
}
if finallyNode != nil {
finallyOp = cp.compoundOp(finallyNode)
finallyOp = cp.primaryOp(finallyNode)
}
return func(ec *EvalCtx) {
body := bodyOp.execMustOneFn(ec)
body := bodyOp.execlambdaOp(ec)
exceptVar := exceptVarOp.execMustOne(ec)
except := exceptOp.execMustOneFn(ec)
else_ := elseOp.execMustOneFn(ec)
finally := finallyOp.execMustOneFn(ec)
except := exceptOp.execlambdaOp(ec)
else_ := elseOp.execlambdaOp(ec)
finally := finallyOp.execlambdaOp(ec)
err := ec.PCall(body, NoArgs, NoOpts)
if err != nil {
@ -423,22 +424,14 @@ func compileTry(cp *compiler, fn *parse.Form) OpFunc {
}
}
// execMustOneFn executes the ValuesOp and raises an exception if it does not
// evaluate to exactly one Fn. If the given ValuesOp is empty, it returns nil.
func (op ValuesOp) execMustOneFn(ec *EvalCtx) Callable {
// execLambdaOp executes a ValuesOp that is known to yield a lambda and returns
// the lambda. If the ValuesOp is empty, it returns a nil.
func (op ValuesOp) execlambdaOp(ec *EvalCtx) Callable {
if op.Func == nil {
return nil
}
values := op.Exec(ec)
if len(values) != 1 {
ec.errorpf(op.Begin, op.End, "should be one fn")
}
fn, ok := values[0].(Callable)
if !ok {
ec.errorpf(op.Begin, op.End, "should be one fn")
}
return fn
return op.Exec(ec)[0].(Callable)
}
// execMustOne executes the LValuesOp and raises an exception if it does not

View File

@ -79,7 +79,7 @@ var evalTests = []struct {
{"if $false { put 2 } elif true { put 2 } else { put 3 }",
strs("2"), nomore},
// try
{"try { nop } except - { put bad } else { put good }", strs("good"), nomore},
{"try { nop } except { put bad } else { put good }", strs("good"), nomore},
{"try { e:false } except - { put bad } else { put good }", strs("bad"), nomore},
// while
{"x=0; while (< $x 4) { put $x; x=(+ $x 1) }",