blob: d9002693e36e437600a1dcb44a752302418943f0 [file] [log] [blame]
// Copyright 2017 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
package parse
// TODO: write a formal grammar for the language.
import (
"fmt"
a "github.com/google/wuffs/lang/ast"
t "github.com/google/wuffs/lang/token"
)
type Options struct {
AllowBuiltInNames bool
AllowDoubleUnderscoreNames bool
}
func validConstName(s string) bool {
if (len(s) >= 2) && (s[0] == '_') && (s[1] == '_') {
return false
}
for i := 0; i < len(s); i++ {
switch c := s[i]; {
default:
return false
case c == '_':
case ('0' <= c) && (c <= '9'):
case ('A' <= c) && (c <= 'Z'): // Upper A-Z but not lower a-z.
}
}
return true
}
func containsDoubleUnderscore(s string) bool {
for i := 1; i < len(s); i++ {
if (s[i-1] == '_') && (s[i] == '_') {
return true
}
}
return false
}
func isStatusMessage(s string) bool {
if s == "" {
return false
}
c := s[0]
return (c == '@') || (c == '#') || (c == '$')
}
func Parse(tm *t.Map, filename string, src []t.Token, opts *Options) (*a.File, error) {
p := &parser{
tm: tm,
filename: filename,
src: src,
}
if len(src) > 0 {
p.lastLine = src[len(src)-1].Line
}
if opts != nil {
p.opts = *opts
}
return p.parseFile()
}
func ParseExpr(tm *t.Map, filename string, src []t.Token, opts *Options) (*a.Expr, error) {
p := &parser{
tm: tm,
filename: filename,
src: src,
}
if len(src) > 0 {
p.lastLine = src[len(src)-1].Line
}
if opts != nil {
p.opts = *opts
}
return p.parseExpr()
}
type parser struct {
tm *t.Map
filename string
src []t.Token
opts Options
lastLine uint32
funcEffect a.Effect
loops a.LoopStack
allowVar bool
}
func (p *parser) line() uint32 {
if len(p.src) != 0 {
return p.src[0].Line
}
return p.lastLine
}
func (p *parser) peek1() t.ID {
if len(p.src) > 0 {
return p.src[0].ID
}
return 0
}
func (p *parser) parseFile() (*a.File, error) {
topLevelDecls := []*a.Node(nil)
for len(p.src) > 0 {
d, err := p.parseTopLevelDecl()
if err != nil {
return nil, err
}
topLevelDecls = append(topLevelDecls, d)
}
return a.NewFile(p.filename, topLevelDecls), nil
}
func (p *parser) parseTopLevelDecl() (*a.Node, error) {
flags := a.Flags(0)
line := p.src[0].Line
switch k := p.peek1(); k {
case t.IDUse:
p.src = p.src[1:]
path := p.peek1()
if !path.IsDQStrLiteral(p.tm) {
got := p.tm.ByID(path)
return nil, fmt.Errorf(`parse: expected "-string literal, got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return a.NewUse(p.filename, line, path).AsNode(), nil
case t.IDPub:
flags |= a.FlagsPublic
fallthrough
case t.IDPri:
p.src = p.src[1:]
switch p.peek1() {
case t.IDConst:
p.src = p.src[1:]
id, err := p.parseIdent()
if err != nil {
return nil, err
}
if !validConstName(p.tm.ByID(id)) {
return nil, fmt.Errorf(`parse: invalid const name %q at %s:%d`,
p.tm.ByID(id), p.filename, p.line())
}
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
typ, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
if p.peek1() != t.IDEq {
return nil, fmt.Errorf(`parse: const %q has no value at %s:%d`,
p.tm.ByID(id), p.filename, p.line())
}
p.src = p.src[1:]
value, err := p.parsePossibleListExpr()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return a.NewConst(flags, p.filename, line, id, typ, value).AsNode(), nil
case t.IDFunc:
p.src = p.src[1:]
id0, id1, err := p.parseQualifiedIdent()
if err != nil {
return nil, err
}
if !p.opts.AllowBuiltInNames {
switch id1 {
case t.IDInitialize, t.IDReset:
return nil, fmt.Errorf(`parse: cannot have a method named %q at %s:%d`,
id1.Str(p.tm), p.filename, p.line())
}
}
// TODO: should we require id0 != 0? In other words, always methods
// (attached to receivers) and never free standing functions?
if !p.opts.AllowDoubleUnderscoreNames && containsDoubleUnderscore(p.tm.ByID(id1)) {
return nil, fmt.Errorf(`parse: double-underscore %q used for func name at %s:%d`,
p.tm.ByID(id1), p.filename, p.line())
}
p.funcEffect = p.parseEffect()
flags |= p.funcEffect.AsFlags()
argFields, err := p.parseList(t.IDCloseParen, (*parser).parseFieldNode)
if err != nil {
return nil, err
}
out := (*a.TypeExpr)(nil)
if x := p.peek1(); (x != t.IDOpenCurly) && (x != t.IDComma) {
out, err = p.parseTypeExpr()
if err != nil {
return nil, err
}
}
asserts := []*a.Node(nil)
if p.peek1() == t.IDComma {
p.src = p.src[1:]
if p.peek1() == t.IDChoosy {
p.src = p.src[1:]
if (flags & a.FlagsPublic) != 0 {
return nil, fmt.Errorf(`parse: choosy function cannot be pub at %s:%d`,
p.filename, p.line())
} else if p.funcEffect.Coroutine() {
return nil, fmt.Errorf(`parse: choosy function cannot be a coroutine at %s:%d`,
p.filename, p.line())
}
flags |= a.FlagsChoosy
if p.peek1() != t.IDOpenCurly {
if x := p.peek1(); x != t.IDComma {
return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`,
p.tm.ByID(x), p.filename, p.line())
}
p.src = p.src[1:]
}
}
asserts, err = p.parseList(t.IDOpenCurly, (*parser).parseAssertNode)
if err != nil {
return nil, err
}
if err := p.assertsSorted(asserts, true); err != nil {
return nil, err
}
for _, o := range asserts {
o := o.AsAssert()
if o.Keyword() != t.IDChoose {
continue
} else if o.IsChooseCPUArch() {
flags |= a.FlagsHasChooseCPUArch
} else {
return nil, fmt.Errorf(`parse: invalid "choose" condition at %s:%d`,
p.filename, p.line())
}
}
}
p.allowVar = true
body, err := p.parseBlock(false)
if err != nil {
return nil, err
}
p.allowVar = false
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if (flags & a.FlagsHasChooseCPUArch) != 0 {
if (flags & a.FlagsPublic) != 0 {
return nil, fmt.Errorf(`parse: cpu_arch function cannot be public at %s:%d`,
p.filename, p.line())
}
if (flags & a.FlagsChoosy) != 0 {
return nil, fmt.Errorf(`parse: cpu_arch function cannot be choosy at %s:%d`,
p.filename, p.line())
}
}
p.funcEffect = 0
in := a.NewStruct(0, p.filename, line, t.IDArgs, nil, argFields)
return a.NewFunc(flags, p.filename, line, id0, id1, in, out, asserts, body).AsNode(), nil
case t.IDStatus:
p.src = p.src[1:]
message := p.peek1()
if !message.IsDQStrLiteral(p.tm) {
got := p.tm.ByID(message)
return nil, fmt.Errorf(`parse: expected "-string literal, got %q at %s:%d`, got, p.filename, p.line())
}
if s, _ := t.Unescape(p.tm.ByID(message)); !isStatusMessage(s) {
return nil, fmt.Errorf(`parse: status message %q does not start with `+
`@, # or $ at %s:%d`, s, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return a.NewStatus(flags, p.filename, line, message).AsNode(), nil
case t.IDStruct:
p.src = p.src[1:]
name, err := p.parseIdent()
if err != nil {
return nil, err
}
if !p.opts.AllowDoubleUnderscoreNames && containsDoubleUnderscore(p.tm.ByID(name)) {
return nil, fmt.Errorf(`parse: double-underscore %q used for struct name at %s:%d`,
p.tm.ByID(name), p.filename, p.line())
}
if p.peek1() == t.IDQuestion {
flags |= a.FlagsClassy
p.src = p.src[1:]
}
implements := []*a.Node(nil)
if p.peek1() == t.IDImplements {
p.src = p.src[1:]
implements, err = p.parseList(t.IDOpenParen, (*parser).parseQualifiedIdentAsTypeExprNode)
if err != nil {
return nil, err
}
if len(implements) > a.MaxImplements {
return nil, fmt.Errorf(`parse: too many implements listed at %s:%d`, p.filename, p.line())
}
}
fields, err := p.parseList(t.IDCloseParen, (*parser).parseFieldNode)
if err != nil {
return nil, err
}
if x := p.peek1(); x == t.IDPlus {
p.src = p.src[1:]
if x := p.peek1(); x != t.IDOpenParen {
return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`,
p.tm.ByID(x), p.filename, p.line())
}
extraFields, err := p.parseList(t.IDCloseParen, (*parser).parseExtraFieldNode)
if err != nil {
return nil, err
}
fields = append(fields, extraFields...)
}
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return a.NewStruct(flags, p.filename, line, name, implements, fields).AsNode(), nil
}
}
return nil, fmt.Errorf(`parse: unrecognized top level declaration at %s:%d`, p.filename, line)
}
func (p *parser) parseQualifiedIdentAsTypeExprNode() (*a.Node, error) {
pkg, name, err := p.parseQualifiedIdent()
if err != nil {
return nil, err
}
return a.NewTypeExpr(0, pkg, name, nil, nil, nil).AsNode(), nil
}
// parseQualifiedIdent parses "foo.bar" or "bar".
func (p *parser) parseQualifiedIdent() (t.ID, t.ID, error) {
x, err := p.parseIdent()
if err != nil {
return 0, 0, err
}
if p.peek1() != t.IDDot {
return 0, x, nil
}
p.src = p.src[1:]
y, err := p.parseIdent()
if err != nil {
return 0, 0, err
}
return x, y, nil
}
func (p *parser) parseIdentAsExprNode() (*a.Node, error) {
id, err := p.parseIdent()
if err != nil {
return nil, err
}
return a.NewExpr(0, 0, id, nil, nil, nil, nil).AsNode(), nil
}
func (p *parser) parseIdent() (t.ID, error) {
if len(p.src) == 0 {
return 0, fmt.Errorf(`parse: expected identifier at %s:%d`, p.filename, p.line())
}
x := p.src[0]
if !x.ID.IsIdent(p.tm) {
got := p.tm.ByID(x.ID)
return 0, fmt.Errorf(`parse: expected identifier, got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return x.ID, nil
}
func (p *parser) parseList(stop t.ID, parseElem func(*parser) (*a.Node, error)) ([]*a.Node, error) {
if stop == t.IDCloseParen {
if x := p.peek1(); x != t.IDOpenParen {
return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`,
p.tm.ByID(x), p.filename, p.line())
}
p.src = p.src[1:]
}
ret := []*a.Node(nil)
for len(p.src) > 0 {
if id := p.src[0].ID; id == stop {
if stop == t.IDCloseParen || stop == t.IDCloseBracket {
p.src = p.src[1:]
}
return ret, nil
} else if (stop == t.IDOpenDoubleCurly) && (id == t.IDOpenCurly) {
return ret, nil
}
elem, err := parseElem(p)
if err != nil {
return nil, err
}
ret = append(ret, elem)
switch x := p.peek1(); x {
case stop:
if stop == t.IDCloseParen || stop == t.IDCloseBracket {
p.src = p.src[1:]
}
return ret, nil
case t.IDComma:
p.src = p.src[1:]
default:
return nil, fmt.Errorf(`parse: expected %q, got %q at %s:%d`,
p.tm.ByID(stop), p.tm.ByID(x), p.filename, p.line())
}
}
return nil, fmt.Errorf(`parse: expected %q at %s:%d`, p.tm.ByID(stop), p.filename, p.line())
}
func (p *parser) parseFieldNode() (*a.Node, error) {
return p.parseFieldNode1(0)
}
func (p *parser) parseExtraFieldNode() (*a.Node, error) {
n, err := p.parseFieldNode1(a.FlagsPrivateData)
if err != nil {
return nil, err
}
typ := n.AsField().XType()
for typ.Decorator() == t.IDArray {
typ = typ.Inner()
}
if (typ.Decorator() != 0) ||
(typ.QID()[0] == t.IDBase) && (!typ.IsNumType() || typ.IsRefined()) {
return nil, fmt.Errorf(`parse: invalid extra-field type %q at %s:%d`,
n.AsField().XType().Str(p.tm), p.filename, p.line())
}
return n, nil
}
func (p *parser) parseFieldNode1(flags a.Flags) (*a.Node, error) {
name, err := p.parseIdent()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
typ, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
if pkg := typ.Innermost().QID()[0]; (pkg != 0) && (pkg != t.IDBase) {
flags |= a.FlagsPrivateData
}
return a.NewField(flags, name, typ).AsNode(), nil
}
func (p *parser) parseTypeExpr() (*a.TypeExpr, error) {
if x := p.peek1(); x == t.IDNptr || x == t.IDPtr {
p.src = p.src[1:]
rhs, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
return a.NewTypeExpr(x, 0, 0, nil, nil, rhs), nil
}
decorator, arrayLength := t.ID(0), (*a.Expr)(nil)
switch peek1 := p.peek1(); peek1 {
case t.IDArray, t.IDRoarray:
decorator = peek1
p.src = p.src[1:]
if x := p.peek1(); x != t.IDOpenBracket {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "[", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
var err error
arrayLength, err = p.parseExpr()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDCloseBracket {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "]", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
case t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
decorator = peek1
p.src = p.src[1:]
}
if decorator != 0 {
rhs, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
return a.NewTypeExpr(decorator, 0, 0, arrayLength.AsNode(), nil, rhs), nil
}
pkg, name, err := p.parseQualifiedIdent()
if err != nil {
return nil, err
}
lhs, mhs := (*a.Expr)(nil), (*a.Expr)(nil)
if p.peek1() == t.IDOpenBracket {
_, lhs, mhs, err = p.parseBracket(t.IDDotDotEq)
if err != nil {
return nil, err
}
if name.IsNumType() &&
((pkg == t.IDBase) || ((pkg == 0) && p.opts.AllowBuiltInNames)) {
// No-op.
} else {
return nil, fmt.Errorf(`parse: cannot refine non-numeric type at %s:%d`, p.filename, p.line())
}
}
return a.NewTypeExpr(0, pkg, name, lhs.AsNode(), mhs, nil), nil
}
// parseBracket parses "[i .. j]", "[i ..]", "[.. j]" and "[..]". A "..="
// replaces the ".." if sep is t.IDDotDotEq instead of t.IDDotDot. If sep is
// t.IDDotDot, it also parses "[x]". The returned op is sep for a range or
// refinement and t.IDOpenBracket for an index.
func (p *parser) parseBracket(sep t.ID) (op t.ID, ei *a.Expr, ej *a.Expr, err error) {
if x := p.peek1(); x != t.IDOpenBracket {
got := p.tm.ByID(x)
return 0, nil, nil, fmt.Errorf(`parse: expected "[", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if p.peek1() != sep {
ei, err = p.parseExpr()
if err != nil {
return 0, nil, nil, err
}
}
switch x := p.peek1(); {
case x == sep:
p.src = p.src[1:]
case x == t.IDCloseBracket && sep == t.IDDotDot:
p.src = p.src[1:]
return a.ExprOperatorIndex, nil, ei, nil
default:
extra := ``
if sep == t.IDDotDot {
extra = ` or "]"`
}
got := p.tm.ByID(x)
return 0, nil, nil, fmt.Errorf(`parse: expected %q%s, got %q at %s:%d`,
p.tm.ByID(sep), extra, got, p.filename, p.line())
}
if p.peek1() != t.IDCloseBracket {
ej, err = p.parseExpr()
if err != nil {
return 0, nil, nil, err
}
}
if x := p.peek1(); x != t.IDCloseBracket {
got := p.tm.ByID(x)
return 0, nil, nil, fmt.Errorf(`parse: expected "]", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return sep, ei, ej, nil
}
func (p *parser) parseBlock(doubleCurly bool) ([]*a.Node, error) {
if doubleCurly {
if x := p.peek1(); x != t.IDOpenDoubleCurly {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "{{", got %q at %s:%d`, got, p.filename, p.line())
}
} else {
if x := p.peek1(); x != t.IDOpenCurly {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "{", got %q at %s:%d`, got, p.filename, p.line())
}
}
p.src = p.src[1:]
block := []*a.Node(nil)
for {
if len(p.src) == 0 {
return nil, fmt.Errorf(`parse: expected "}" or "}}" at %s:%d`, p.filename, p.line())
}
if doubleCurly {
if p.src[0].ID == t.IDCloseDoubleCurly {
break
}
} else {
if p.src[0].ID == t.IDCloseCurly {
break
}
}
s, err := p.parseStatement()
if err != nil {
return nil, err
}
block = append(block, s)
if x := p.peek1(); x != t.IDSemicolon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected (implicit) ";", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
}
p.src = p.src[1:]
return block, nil
}
func (p *parser) assertsSorted(asserts []*a.Node, allowChoose bool) error {
seenPre, seenInv, seenPost := false, false, false
for _, o := range asserts {
switch o.AsAssert().Keyword() {
case t.IDAssert:
return fmt.Errorf(`parse: assertion chain cannot contain "assert", `+
`only "pre", "inv" and "post" at %s:%d`, p.filename, p.line())
case t.IDChoose:
if !allowChoose {
return fmt.Errorf(`parse: invalid "choose" at %s:%d`, p.filename, p.line())
}
if seenPre || seenPost || seenInv {
break
}
continue
case t.IDPre:
if seenPost || seenInv {
break
}
seenPre = true
continue
case t.IDInv:
if seenPost {
break
}
seenInv = true
continue
default:
seenPost = true
continue
}
return fmt.Errorf(`parse: assertion chain not in "choose", "pre", "inv", "post" order at %s:%d`,
p.filename, p.line())
}
return nil
}
func (p *parser) parseAssertNode() (*a.Node, error) {
switch x := p.peek1(); x {
case t.IDAssert, t.IDChoose, t.IDPre, t.IDInv, t.IDPost:
p.src = p.src[1:]
condition, err := p.parseExpr()
if err != nil {
return nil, err
}
if condition.Effect() != 0 {
return nil, fmt.Errorf(`parse: assert-condition %q is not effect-free at %s:%d`,
condition.Str(p.tm), p.filename, p.line())
}
reason, args := t.ID(0), []*a.Node(nil)
if p.peek1() == t.IDVia {
p.src = p.src[1:]
reason = p.peek1()
if !reason.IsDQStrLiteral(p.tm) {
got := p.tm.ByID(reason)
return nil, fmt.Errorf(`parse: expected "-string literal, got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
args, err = p.parseList(t.IDCloseParen, (*parser).parseArgNode)
if err != nil {
return nil, err
}
}
return a.NewAssert(x, condition, reason, args).AsNode(), nil
}
return nil, fmt.Errorf(`parse: expected "assert", "pre" or "post" at %s:%d`, p.filename, p.line())
}
func (p *parser) parseStatement() (*a.Node, error) {
line := uint32(0)
if len(p.src) > 0 {
line = p.src[0].Line
}
n, err := p.parseStatement1()
if n != nil {
n.AsRaw().SetFilenameLine(p.filename, line)
if n.Kind() == a.KIterate {
for _, o := range n.AsIterate().Assigns() {
o.AsRaw().SetFilenameLine(p.filename, line)
}
}
}
return n, err
}
func (p *parser) parseLabel() (t.ID, error) {
if p.peek1() == t.IDDot {
p.src = p.src[1:]
return p.parseIdent()
}
return 0, nil
}
func (p *parser) parseStatement1() (*a.Node, error) {
x := p.peek1()
if x == t.IDVar {
if !p.allowVar {
return nil, fmt.Errorf(`parse: var statement not at the top of a function at %s:%d`,
p.filename, p.line())
}
p.src = p.src[1:]
return p.parseVarNode()
}
p.allowVar = false
switch x {
case t.IDAssert:
return p.parseAssertNode()
case t.IDBreak, t.IDContinue:
p.src = p.src[1:]
label, err := p.parseLabel()
if err != nil {
return nil, err
}
loop := a.Loop(nil)
if len(p.loops) == 0 {
// No-op.
} else if label == 0 {
loop = p.loops.Top()
if loop.Label() != 0 {
return nil, fmt.Errorf(`parse: unlabeled %s for labeled %s.%s at %s:%d`,
x.Str(p.tm), loop.Keyword().Str(p.tm), loop.Label().Str(p.tm), p.filename, p.line())
}
} else {
for i := len(p.loops) - 1; i >= 0; i-- {
if l := p.loops[i]; label == l.Label() {
loop = l
break
}
}
}
if loop == nil {
sepStr, labelStr := "", ""
if label != 0 {
sepStr, labelStr = ".", label.Str(p.tm)
}
return nil, fmt.Errorf(`parse: no matching while/iterate statement for %s%s%s at %s:%d`,
x.Str(p.tm), sepStr, labelStr, p.filename, p.line())
}
deep := loop != p.loops.Top()
if x == t.IDBreak {
loop.SetHasBreak(deep)
} else {
loop.SetHasContinue(deep)
}
n := a.NewJump(x, label)
n.SetJumpTarget(loop)
return n.AsNode(), nil
case t.IDChoose:
p.src = p.src[1:]
if p.funcEffect.Pure() {
return nil, fmt.Errorf(`parse: choose within pure function at %s:%d`, p.filename, p.line())
}
name, err := p.parseIdent()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDEq {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "=", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDOpenBracket {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "[", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
args, err := p.parseList(t.IDCloseBracket, (*parser).parseIdentAsExprNode)
if err != nil {
return nil, err
}
return a.NewChoose(name, args).AsNode(), nil
case t.IDIOBind, t.IDIOForgetHistory, t.IDIOLimit:
return p.parseIOManipNode()
case t.IDIf:
o, err := p.parseIf()
return o.AsNode(), err
case t.IDIterate:
return p.parseIterateNode()
case t.IDReturn, t.IDYield:
p.src = p.src[1:]
if x == t.IDYield {
if !p.funcEffect.Coroutine() {
return nil, fmt.Errorf(`parse: yield within non-coroutine at %s:%d`, p.filename, p.line())
}
if p.peek1() != t.IDQuestion {
return nil, fmt.Errorf(`parse: yield not followed by '?' at %s:%d`, p.filename, p.line())
}
p.src = p.src[1:]
}
value, err := p.parseExpr()
if err != nil {
return nil, err
}
if value.Effect().Impure() {
return nil, fmt.Errorf(`parse: %s an impure expression at %s:%d`,
x.Str(p.tm), p.filename, p.line())
}
if (x == t.IDReturn) && (value.Operator() == 0) {
if s := p.tm.ByID(value.Ident()); (len(s) > 1) && (s[0] == '"') && (s[1] == '$') {
return nil, fmt.Errorf(`parse: cannot return a suspension at %s:%d`, p.filename, p.line())
}
}
return a.NewRet(x, value).AsNode(), nil
case t.IDWhile:
p.src = p.src[1:]
label, err := p.parseLabel()
if err != nil {
return nil, err
}
condition, err := p.parseExpr()
if err != nil {
return nil, err
}
if condition.Effect() != 0 {
return nil, fmt.Errorf(`parse: while-condition %q is not effect-free at %s:%d`,
condition.Str(p.tm), p.filename, p.line())
}
asserts, err := p.parseAsserts()
if err != nil {
return nil, err
}
n := a.NewWhile(label, condition, asserts)
if !p.loops.Push(n) {
return nil, fmt.Errorf(`parse: duplicate loop label %s at %s:%d`,
label.Str(p.tm), p.filename, p.line())
}
doubleCurly := p.peek1() == t.IDOpenDoubleCurly
if doubleCurly && !n.IsWhileTrue() {
return nil, fmt.Errorf(`parse: double {{ }} while loop condition isn't "true" at %s:%d`,
p.filename, p.line())
}
body, err := p.parseBlock(doubleCurly)
if err != nil {
return nil, err
}
n.SetBody(body)
p.loops.Pop()
if label != 0 {
seenDotLabel := false
if p.peek1() == t.IDDot {
p.src = p.src[1:]
if p.peek1() == label {
p.src = p.src[1:]
seenDotLabel = true
}
}
if !seenDotLabel {
return nil, fmt.Errorf(`parse: expected .%s at %s:%d`,
label.Str(p.tm), p.filename, p.line())
}
}
if !doubleCurly {
// No-op.
} else if n.HasContinue() {
return nil, fmt.Errorf(`parse: double {{ }} while loop has explicit continue at %s:%d`,
p.filename, p.line())
} else if !a.Terminates(body) {
return nil, fmt.Errorf(`parse: double {{ }} while loop doesn't terminate at %s:%d`,
p.filename, p.line())
}
return n.AsNode(), nil
}
return p.parseAssignNode()
}
func (p *parser) parseAssignNode() (*a.Node, error) {
lhs := (*a.Expr)(nil)
rhs, err := p.parseExpr()
if err != nil {
return nil, err
}
op := p.peek1()
if op.IsAssign() {
p.src = p.src[1:]
lhs = rhs
if lhs.Effect() != 0 {
return nil, fmt.Errorf(`parse: assignment LHS %q is not effect-free at %s:%d`,
lhs.Str(p.tm), p.filename, p.line())
}
for l := lhs; l != nil; l = l.LHS().AsExpr() {
switch l.Operator() {
case 0:
if id := l.Ident(); id.IsLiteral(p.tm) {
return nil, fmt.Errorf(`parse: assignment LHS %q is a literal at %s:%d`,
l.Str(p.tm), p.filename, p.line())
} else if id.IsCannotAssignTo() {
if l == lhs {
return nil, fmt.Errorf(`parse: cannot assign to %q at %s:%d`,
id.Str(p.tm), p.filename, p.line())
}
if !p.funcEffect.Impure() {
return nil, fmt.Errorf(`parse: cannot assign to %q in a pure function at %s:%d`,
lhs.Str(p.tm), p.filename, p.line())
}
}
case t.IDDot, t.IDOpenBracket:
// No-op.
default:
return nil, fmt.Errorf(`parse: invalid assignment LHS %q at %s:%d`,
lhs.Str(p.tm), p.filename, p.line())
}
}
rhs, err = p.parseExpr()
if err != nil {
return nil, err
}
if op == t.IDEqQuestion {
if (rhs.Operator() != a.ExprOperatorCall) || (!rhs.Effect().Coroutine()) {
return nil, fmt.Errorf(`parse: expected ?-function call after "=?", got %q at %s:%d`,
rhs.Str(p.tm), p.filename, p.line())
}
}
} else {
op = t.IDEq
}
if p.funcEffect.WeakerThan(rhs.Effect()) {
return nil, fmt.Errorf(`parse: value %q's effect %q is stronger than the func's effect %q at %s:%d`,
rhs.Str(p.tm), rhs.Effect(), p.funcEffect, p.filename, p.line())
}
return a.NewAssign(op, lhs, rhs).AsNode(), nil
}
func (p *parser) parseIterateAssignNode() (*a.Node, error) {
n, err := p.parseAssignNode()
if err != nil {
return nil, err
}
o := n.AsAssign()
if op := o.Operator(); op != t.IDEq {
return nil, fmt.Errorf(`parse: expected "=", got %q at %s:%d`, op.Str(p.tm), p.filename, p.line())
}
if lhs := o.LHS(); lhs.Operator() != 0 {
return nil, fmt.Errorf(`parse: expected variable, got %q at %s:%d`, lhs.Str(p.tm), p.filename, p.line())
}
if rhs := o.RHS(); rhs.Effect() != 0 {
return nil, fmt.Errorf(`parse: value %q is not effect-free at %s:%d`, rhs.Str(p.tm), p.filename, p.line())
}
return o.AsNode(), nil
}
func (p *parser) parseAsserts() ([]*a.Node, error) {
asserts := []*a.Node(nil)
if p.peek1() == t.IDComma {
p.src = p.src[1:]
var err error
if asserts, err = p.parseList(t.IDOpenDoubleCurly, (*parser).parseAssertNode); err != nil {
return nil, err
}
if err := p.assertsSorted(asserts, false); err != nil {
return nil, err
}
}
return asserts, nil
}
func (p *parser) parseIOManipNode() (*a.Node, error) {
keyword := p.peek1()
p.src = p.src[1:]
if x := p.peek1(); x != t.IDOpenParen {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDIO {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "io", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
io, err := p.parseExpr()
if err != nil {
return nil, err
}
if io.Effect() != 0 {
return nil, fmt.Errorf(`parse: argument %q is not effect-free at %s:%d`,
io.Str(p.tm), p.filename, p.line())
}
arg1Name := t.ID(0)
switch keyword {
case t.IDIOBind:
arg1Name = t.IDData
if io.Operator() != 0 {
return nil, fmt.Errorf(`parse: invalid %s argument %q at %s:%d`,
keyword.Str(p.tm), io.Str(p.tm), p.filename, p.line())
}
case t.IDIOForgetHistory:
// No-op.
case t.IDIOLimit:
arg1Name = t.IDLimit
if (io.Operator() != 0) && (io.IsArgsDotFoo() == 0) {
return nil, fmt.Errorf(`parse: invalid %s argument %q at %s:%d`,
keyword.Str(p.tm), io.Str(p.tm), p.filename, p.line())
}
}
arg1 := (*a.Expr)(nil)
if arg1Name != 0 {
if x := p.peek1(); x != t.IDComma {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != arg1Name {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected %q, got %q at %s:%d`, arg1Name.Str(p.tm), got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
arg1, err = p.parseExpr()
if err != nil {
return nil, err
}
if arg1.Effect() != 0 {
return nil, fmt.Errorf(`parse: argument %q is not effect-free at %s:%d`,
io.Str(p.tm), p.filename, p.line())
}
}
histPos := (*a.Expr)(nil)
if keyword == t.IDIOBind {
if x := p.peek1(); x != t.IDComma {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDHistoryPosition {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "history_position", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
histPos, err = p.parseExpr()
if err != nil {
return nil, err
}
if histPos.Effect() != 0 {
return nil, fmt.Errorf(`parse: argument %q is not effect-free at %s:%d`,
io.Str(p.tm), p.filename, p.line())
}
}
if x := p.peek1(); x != t.IDCloseParen {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
body, err := p.parseBlock(false)
if err != nil {
return nil, err
}
return a.NewIOManip(keyword, io, arg1, histPos, body).AsNode(), nil
}
func (p *parser) parseIf() (*a.If, error) {
if x := p.peek1(); x != t.IDIf {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "if", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
likelihood, err := p.parseLabel()
if err != nil {
return nil, err
}
switch likelihood {
case 0, t.IDLikely, t.IDUnlikely:
default:
got := p.tm.ByID(likelihood)
return nil, fmt.Errorf(`parse: expected "if.likely" or "if.unlikely", got %q at %s:%d`, "if."+got, p.filename, p.line())
}
condition, err := p.parseExpr()
if err != nil {
return nil, err
}
if condition.Effect() != 0 {
return nil, fmt.Errorf(`parse: if-condition %q is not effect-free at %s:%d`,
condition.Str(p.tm), p.filename, p.line())
}
bodyIfTrue, err := p.parseBlock(false)
if err != nil {
return nil, err
}
elseIf, bodyIfFalse := (*a.If)(nil), ([]*a.Node)(nil)
if p.peek1() == t.IDElse {
p.src = p.src[1:]
if p.peek1() == t.IDIf {
elseIf, err = p.parseIf()
if err != nil {
return nil, err
}
} else {
bodyIfFalse, err = p.parseBlock(false)
if err != nil {
return nil, err
}
}
}
return a.NewIf(likelihood, condition, bodyIfTrue, bodyIfFalse, elseIf), nil
}
func (p *parser) parseIterateNode() (*a.Node, error) {
if p.funcEffect.Coroutine() {
return nil, fmt.Errorf(`parse: "iterate" inside coroutine at %s:%d`, p.filename, p.line())
} else if x := p.peek1(); x != t.IDIterate {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "iterate", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
label, err := p.parseLabel()
if err != nil {
return nil, err
}
assigns, err := p.parseList(t.IDCloseParen, (*parser).parseIterateAssignNode)
if err != nil {
return nil, err
}
n, err := p.parseIterateBlock(label, assigns)
if err != nil {
return nil, err
}
return n.AsNode(), nil
}
func (p *parser) parseIterateBlock(label t.ID, assigns []*a.Node) (*a.Iterate, error) {
if x := p.peek1(); x != t.IDOpenParen {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "(", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDLength {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "length", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
length := p.peek1()
lengthInt := asSmallPositiveInt256(p.tm, length)
if lengthInt == 0 {
return nil, fmt.Errorf(`parse: expected length count in [1 ..= 256], got %q at %s:%d`,
p.tm.ByID(length), p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDComma {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDAdvance {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "advance", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
advance := p.peek1()
advanceInt := asSmallPositiveInt256(p.tm, advance)
if advanceInt == 0 {
return nil, fmt.Errorf(`parse: expected advance count in [1 ..= 256], got %q at %s:%d`,
p.tm.ByID(advance), p.filename, p.line())
} else if advanceInt > lengthInt {
return nil, fmt.Errorf(`parse: advance %d is larger than length %d at %s:%d`,
advanceInt, lengthInt, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDComma {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ",", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDUnroll {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected "unroll", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
unroll := p.peek1()
if asSmallPositiveInt256(p.tm, unroll) == 0 {
return nil, fmt.Errorf(`parse: expected unroll count in [1 ..= 256], got %q at %s:%d`,
p.tm.ByID(unroll), p.filename, p.line())
}
p.src = p.src[1:]
if x := p.peek1(); x != t.IDCloseParen {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
asserts, err := p.parseAsserts()
if err != nil {
return nil, err
}
n := a.NewIterate(label, assigns, length, advance, unroll, asserts)
// TODO: decide how break/continue work with iterate loops.
if !p.loops.Push(n) {
return nil, fmt.Errorf(`parse: duplicate loop label %s at %s:%d`,
label.Str(p.tm), p.filename, p.line())
}
body, err := p.parseBlock(false)
if err != nil {
return nil, err
}
n.SetBody(body)
p.loops.Pop()
if x := p.peek1(); x == t.IDElse {
p.src = p.src[1:]
elseIterate, err := p.parseIterateBlock(0, nil)
if err != nil {
return nil, err
}
n.SetElseIterate(elseIterate)
}
return n, nil
}
// asSmallPositiveInt256 returns id's value when id is a numeric literal in the
// range [1 ..= 256]. Otherwise, it returns 0.
func asSmallPositiveInt256(tm *t.Map, id t.ID) int {
if !id.IsNumLiteral(tm) {
return 0
}
s := id.Str(tm)
if (len(s) > 3) || (len(s) == 0) || (s[0] < '1') || ('9' < s[0]) {
return 0
}
n, s := int(s[0]-'0'), s[1:]
for ; len(s) > 0; s = s[1:] {
if (s[0] < '0') || ('9' < s[0]) {
return 0
}
n = (10 * n) + int(s[0]-'0')
}
if n > 256 {
return 0
}
return n
}
func (p *parser) parseArgNode() (*a.Node, error) {
name, err := p.parseIdent()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
value, err := p.parseExpr()
if err != nil {
return nil, err
}
if value.Effect() != 0 {
return nil, fmt.Errorf(`parse: arg-value %q is not effect-free at %s:%d`,
value.Str(p.tm), p.filename, p.line())
}
return a.NewArg(name, value).AsNode(), nil
}
func (p *parser) parseVarNode() (*a.Node, error) {
id, err := p.parseIdent()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDColon {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ":", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
typ, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
return a.NewVar(id, typ).AsNode(), nil
}
func (p *parser) parsePossibleListExprNode() (*a.Node, error) {
n, err := p.parsePossibleListExpr()
if err != nil {
return nil, err
}
return n.AsNode(), err
}
func (p *parser) parsePossibleListExpr() (*a.Expr, error) {
// TODO: put the [ and ] parsing into parseExpr.
if x := p.peek1(); x != t.IDOpenBracket {
return p.parseExpr()
}
p.src = p.src[1:]
args, err := p.parseList(t.IDCloseBracket, (*parser).parsePossibleListExprNode)
if err != nil {
return nil, err
}
return a.NewExpr(0, a.ExprOperatorList, 0, nil, nil, nil, args), nil
}
func (p *parser) parseExpr() (*a.Expr, error) {
e, err := p.parseExpr1()
if err != nil {
return nil, err
}
if e.SubExprHasEffect() {
return nil, fmt.Errorf(`parse: expression %q has an effect-ful sub-expression at %s:%d`,
e.Str(p.tm), p.filename, p.line())
}
return e, nil
}
func (p *parser) parseExpr1() (*a.Expr, error) {
lhs, err := p.parseOperand()
if err != nil {
return nil, err
}
if x := p.peek1(); x.IsBinaryOp() {
p.src = p.src[1:]
rhs := (*a.Node)(nil)
if x == t.IDAs {
o, err := p.parseTypeExpr()
if err != nil {
return nil, err
}
rhs = o.AsNode()
} else {
o, err := p.parseOperand()
if err != nil {
return nil, err
}
rhs = o.AsNode()
}
if !x.IsAssociativeOp() || x != p.peek1() {
op := x.BinaryForm()
if op == 0 {
return nil, fmt.Errorf(`parse: internal error: no binary form for token 0x%02X`, x)
}
return a.NewExpr(0, op, 0, lhs.AsNode(), nil, rhs, nil), nil
}
args := []*a.Node{lhs.AsNode(), rhs}
for p.peek1() == x {
p.src = p.src[1:]
arg, err := p.parseOperand()
if err != nil {
return nil, err
}
args = append(args, arg.AsNode())
}
op := x.AssociativeForm()
if op == 0 {
return nil, fmt.Errorf(`parse: internal error: no associative form for token 0x%02X`, x)
}
return a.NewExpr(0, op, 0, nil, nil, nil, args), nil
}
return lhs, nil
}
func (p *parser) parseOperand() (*a.Expr, error) {
switch x := p.peek1(); {
case x.IsUnaryOp():
p.src = p.src[1:]
rhs, err := p.parseOperand()
if err != nil {
return nil, err
}
op := x.UnaryForm()
if op == 0 {
return nil, fmt.Errorf(`parse: internal error: no unary form for token 0x%02X`, x)
}
return a.NewExpr(0, op, 0, nil, nil, rhs.AsNode(), nil), nil
case x.IsLiteral(p.tm):
p.src = p.src[1:]
return a.NewExpr(0, 0, x, nil, nil, nil, nil), nil
case x == t.IDOpenParen:
p.src = p.src[1:]
expr, err := p.parseExpr()
if err != nil {
return nil, err
}
if x := p.peek1(); x != t.IDCloseParen {
got := p.tm.ByID(x)
return nil, fmt.Errorf(`parse: expected ")", got %q at %s:%d`, got, p.filename, p.line())
}
p.src = p.src[1:]
return expr, nil
}
id, err := p.parseIdent()
if err != nil {
return nil, err
}
lhs := a.NewExpr(0, 0, id, nil, nil, nil, nil)
for first := true; ; first = false {
flags := a.Flags(0)
switch p.peek1() {
default:
return lhs, nil
case t.IDExclam, t.IDQuestion:
flags |= p.parseEffect().AsFlags()
fallthrough
case t.IDOpenParen:
args, err := p.parseList(t.IDCloseParen, (*parser).parseArgNode)
if err != nil {
return nil, err
}
lhs = a.NewExpr(flags, a.ExprOperatorCall, 0, lhs.AsNode(), nil, nil, args)
case t.IDOpenBracket:
id0, mhs, rhs, err := p.parseBracket(t.IDDotDot)
if err != nil {
return nil, err
}
lhs = a.NewExpr(0, id0, 0, lhs.AsNode(), mhs.AsNode(), rhs.AsNode(), nil)
case t.IDDot:
p.src = p.src[1:]
selector := p.peek1()
if first && selector.IsDQStrLiteral(p.tm) {
p.src = p.src[1:]
} else {
selector, err = p.parseIdent()
if err != nil {
return nil, err
}
}
lhs = a.NewExpr(0, a.ExprOperatorSelector, selector, lhs.AsNode(), nil, nil, nil)
}
}
}
func (p *parser) parseEffect() a.Effect {
switch p.peek1() {
case t.IDExclam:
p.src = p.src[1:]
return a.EffectImpure
case t.IDQuestion:
p.src = p.src[1:]
return a.EffectImpureCoroutine
}
return 0
}