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) return aw.idx < len(aw.form.Args)
} }
func (aw *argsWalker) next() *parse.Compound { func (aw *argsWalker) peek() *parse.Compound {
if !aw.more() { if !aw.more() {
aw.cp.errorpf(aw.form.End(), aw.form.End(), "need more arguments") 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++ aw.idx++
return aw.form.Args[aw.idx-1] return n
} }
// nextIs returns whether the next argument's source matches the given text. It // 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 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) { if aw.nextIs(leader) {
return aw.next() return aw.nextMustLambda()
} }
return nil return nil
} }

View File

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