Skip to content

Commit

Permalink
cue/ast, cue/parser: parse function types
Browse files Browse the repository at this point in the history
Add ast.Func, an experimental type representing parsed function
types and make cue/parser aware of function types controlled by the
experimental option parser.ParseFuncs.

As functions are experimental, and so far only needed for Wasm,
this CL does not make functions available any other way apart from
using the parser directly.

The grammar accepted by the parser is:

    func  := "func" "(" [ Expression { "," Expression } ] ")" ":" Expression

Change-Id: Ia11a179f7db2feb649ecba77eb2ccf5441e637aa
Signed-off-by: Aram Hăvărneanu <aram@mgk.ro>
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/551041
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
Unity-Result: CUEcueckoo <cueckoo@cuelang.org>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
  • Loading branch information
4ad committed Mar 16, 2023
1 parent 34a27bb commit 8f0fe77
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 15 deletions.
15 changes: 15 additions & 0 deletions cue/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,18 @@ type Interpolation struct {
label
}

// A Func node represents a function type.
//
// This is an experimental type and the contents will change without notice.
type Func struct {
Func token.Pos // position of "func"
Args []Expr // list of elements; or nil
Ret Expr // return type, must not be nil

comments
expr
}

// A StructLit node represents a literal struct.
type StructLit struct {
Lbrace token.Pos // position of "{"
Expand Down Expand Up @@ -748,6 +760,8 @@ func (x *BasicLit) Pos() token.Pos { return x.ValuePos }
func (x *BasicLit) pos() *token.Pos { return &x.ValuePos }
func (x *Interpolation) Pos() token.Pos { return x.Elts[0].Pos() }
func (x *Interpolation) pos() *token.Pos { return x.Elts[0].pos() }
func (x *Func) Pos() token.Pos { return x.Func }
func (x *Func) pos() *token.Pos { return &x.Func }
func (x *StructLit) Pos() token.Pos { return getPos(x) }
func (x *StructLit) pos() *token.Pos {
if x.Lbrace == token.NoPos && len(x.Elts) > 0 {
Expand Down Expand Up @@ -790,6 +804,7 @@ func (x *Ident) End() token.Pos {
func (x *BasicLit) End() token.Pos { return x.ValuePos.Add(len(x.Value)) }

func (x *Interpolation) End() token.Pos { return x.Elts[len(x.Elts)-1].Pos() }
func (x *Func) End() token.Pos { return x.Ret.End() }
func (x *StructLit) End() token.Pos {
if x.Rbrace == token.NoPos && len(x.Elts) > 0 {
return x.Elts[len(x.Elts)-1].End()
Expand Down
4 changes: 4 additions & 0 deletions cue/ast/astutil/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func walk(v visitor, node ast.Node) {
walk(v, a)
}

case *ast.Func:
walkExprList(v, n.Args)
walk(v, n.Ret)

case *ast.StructLit:
for _, f := range n.Elts {
walk(v, f)
Expand Down
4 changes: 4 additions & 0 deletions cue/ast/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func walk(v visitor, node Node) {
walk(v, a)
}

case *Func:
walkExprList(v, n.Args)
walk(v, n.Ret)

case *StructLit:
walkDeclList(v, n.Elts)

Expand Down
10 changes: 10 additions & 0 deletions cue/parser/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ var (
p.mode |= parseCommentsMode
}

// ParseFuncs causes function declarations to be parsed.
//
// This is an experimental function and the API is likely to
// change or dissapear.
ParseFuncs Option = parseFuncs
parseFuncs = func(p *parser) {
p.mode |= parseFuncsMode
}

// Trace causes parsing to print a trace of parsed productions.
Trace Option = traceOpt
traceOpt = func(p *parser) {
Expand Down Expand Up @@ -122,6 +131,7 @@ const (
packageClauseOnlyMode mode = 1 << iota // stop parsing after package clause
importsOnlyMode // stop parsing after import declarations
parseCommentsMode // parse comments and add them to AST
parseFuncsMode // parse function declarations (experimental)
partialMode
traceMode // print a trace of parsed productions
declarationErrorsMode // report declaration errors
Expand Down
69 changes: 67 additions & 2 deletions cue/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,13 @@ func (p *parser) parseOperand() (expr ast.Expr) {
case token.LBRACK:
return p.parseList()

case token.FUNC:
if p.mode&parseFuncsMode != 0 {
return p.parseFunc()
} else {
return p.parseKeyIdent()
}

case token.BOTTOM:
c := p.openComments()
x := &ast.BottomLit{Bottom: p.pos}
Expand Down Expand Up @@ -987,7 +994,7 @@ func (p *parser) parseLabel(rhs bool) (label ast.Label, expr ast.Expr, decl ast.
expr = ident

case token.IDENT, token.STRING, token.INTERPOLATION, token.LPAREN,
token.NULL, token.TRUE, token.FALSE, token.IN:
token.NULL, token.TRUE, token.FALSE, token.IN, token.FUNC:
expr = p.parseExpr()

case token.LBRACK:
Expand All @@ -1002,7 +1009,7 @@ func (p *parser) parseLabel(rhs bool) (label ast.Label, expr ast.Expr, decl ast.
switch x := expr.(type) {
case *ast.BasicLit:
switch x.Kind {
case token.STRING, token.NULL, token.TRUE, token.FALSE:
case token.STRING, token.NULL, token.TRUE, token.FALSE, token.FUNC:
// Keywords that represent operands.

// Allowing keywords to be used as a labels should not interfere with
Expand Down Expand Up @@ -1156,6 +1163,63 @@ func (p *parser) parseComprehensionClauses(first bool) (clauses []ast.Clause, c
}
}

func (p *parser) parseFunc() (expr ast.Expr) {
if p.trace {
defer un(trace(p, "Func"))
}
tok := p.tok
pos := p.pos
fun := p.expect(token.FUNC)

// "func" might be used as an identifier, in which case bail out early.
switch p.tok {
case token.COLON, token.BIND, token.OPTION,
token.COMMA, token.EOF:

return &ast.Ident{
NamePos: pos,
Name: tok.String(),
}
}

p.expect(token.LPAREN)
args := p.parseFuncArgs()
p.expectClosing(token.RPAREN, "argument type list")

p.expect(token.COLON)
ret := p.parseExpr()

return &ast.Func{
Func: fun,
Args: args,
Ret: ret,
}
}

func (p *parser) parseFuncArgs() (list []ast.Expr) {
if p.trace {
defer un(trace(p, "FuncArgs"))
}
p.openList()
defer p.closeList()

for p.tok != token.RPAREN && p.tok != token.EOF {
list = append(list, p.parseFuncArg())
if p.tok != token.RPAREN {
p.expectComma()
}
}

return list
}

func (p *parser) parseFuncArg() (expr ast.Expr) {
if p.trace {
defer un(trace(p, "FuncArg"))
}
return p.parseExpr()
}

func (p *parser) parseList() (expr ast.Expr) {
lbrack := p.expect(token.LBRACK)

Expand Down Expand Up @@ -1290,6 +1354,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
case *ast.Ident:
case *ast.BasicLit:
case *ast.Interpolation:
case *ast.Func:
case *ast.StructLit:
case *ast.ListLit:
case *ast.ParenExpr:
Expand Down
34 changes: 25 additions & 9 deletions cue/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,18 @@ func TestParse(t *testing.T) {
`, `true, false, null, for, in, if, let, if`,
}, {
"keywords as labels",
`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5
for: if: let: 3
`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5, func: 6
for: if: func: let: 3
`,
`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5, for: {if: {let: 3}}`,
`if: 0, for: 1, in: 2, where: 3, div: 4, quo: 5, func: 6, for: {if: {func: {let: 3}}}`,
}, {
"keywords as alias",
`if=foo: 0
for=bar: 2
let=bar: 3
func=baz: 4
`,
`if=foo: 0, for=bar: 2, let=bar: 3`,
`if=foo: 0, for=bar: 2, let=bar: 3, func=baz: 4`,
}, {
"json",
`{
Expand Down Expand Up @@ -606,13 +607,13 @@ bar: 2
in: `
struct: {
// This is a comment
// This is a comment
// Another comment
something: {
}
// extra comment
}`,
out: `struct: {<[0// This is a comment] [0// This is a comment] [d0// Another comment] [d5// extra comment] something: {}>}`,
Expand All @@ -636,12 +637,12 @@ bar: 2
in: `
funcArg1: foo(
{},
// Comment1
// Comment2
{}
// Comment3
)`,
out: "funcArg1: foo(<[1// Comment1] {}>, <[d0// Comment2] [d1// Comment3] {}>)",
Expand All @@ -654,13 +655,28 @@ bar: 2
}
`,
out: "frontStyle: {\"key\": \"value\", \"key2\": \"value2\", \"foo\": bar}",
}, {
desc: "function types",
in: `
f0: func(): int
f1: func(int): int
f2: func(int, string): int
f3: func({a: int, b: string}): bool
f4: func(bool, func(int, string): int): string
f5: func(int, int): func(bool, bool): bool
f6: func(func(bool, bool): bool, func(string, string): string): func(int, func(int, string): int): func(int, string): int
`,
out: "f0: func(): int, f1: func(int): int, f2: func(int, string): int, f3: func({a: int, b: string}): bool, f4: func(bool, func(int, string): int): string, f5: func(int, int): func(bool, bool): bool, f6: func(func(bool, bool): bool, func(string, string): string): func(int, func(int, string): int): func(int, string): int",
}}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
mode := []Option{AllErrors}
if strings.Contains(tc.desc, "comments") {
mode = append(mode, ParseComments)
}
if strings.Contains(tc.desc, "function") {
mode = append(mode, ParseFuncs)
}
f, err := ParseFile("input", tc.in, mode...)
got := debugStr(f)
if err != nil {
Expand Down
10 changes: 6 additions & 4 deletions cue/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const (
FOR
IN
LET
FUNC // experimental

TRUE
FALSE
Expand Down Expand Up @@ -168,10 +169,11 @@ var tokens = [...]string{
TRUE: "true",
NULL: "null",

FOR: "for",
IF: "if",
IN: "in",
LET: "let",
FOR: "for",
IF: "if",
IN: "in",
LET: "let",
FUNC: "func",
}

// String returns the string corresponding to the token tok.
Expand Down
3 changes: 3 additions & 0 deletions internal/astinternal/debugstr.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ func DebugStr(x interface{}) (out string) {
out += DebugStr(v.Path)
return out

case *ast.Func:
return fmt.Sprintf("func(%v): %v", DebugStr(v.Args), DebugStr(v.Ret))

case []ast.Decl:
if len(v) == 0 {
return ""
Expand Down
10 changes: 10 additions & 0 deletions internal/core/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,16 @@ func (c *compiler) expr(expr ast.Expr) adt.Expr {
case *ast.Ident:
return c.resolve(n)

case *ast.Func:
// We don't yet support function types natively in
// CUE. ast.Func exists only to support external
// interpreters. Function values (really, adt.Builtin)
// are only created by the runtime, or injected by
// external interpreters.
//
// TODO: revise this when we add function types.
return c.resolve(ast.NewIdent("_"))

case *ast.StructLit:
c.pushScope(nil, 1, n)
v := &adt.StructLit{Src: n}
Expand Down

0 comments on commit 8f0fe77

Please sign in to comment.