Skip to content

Commit 057fa1a

Browse files
authored
Add parse only evaluation to REPL (#1254)
Add parse only evaluation to REPL
1 parent e686da8 commit 057fa1a

15 files changed

Lines changed: 1134 additions & 950 deletions

repl/commands.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ type parseCmd struct {
102102
}
103103

104104
type evalCmd struct {
105-
expr string
105+
parseOnly bool
106+
expr string
106107
}
107108

108109
// Cmder interface provides normalized command name from a repl command.
@@ -308,7 +309,18 @@ func (c *commandParseListener) EnterParse(ctx *parser.ParseContext) {
308309
}
309310

310311
func (c *commandParseListener) EnterExprCmd(ctx *parser.ExprCmdContext) {
311-
c.cmd = &evalCmd{}
312+
cmd := &evalCmd{}
313+
for _, f := range ctx.GetFlags() {
314+
ft := strings.TrimPrefix(strings.TrimPrefix(f.GetText(), "--"), "-")
315+
switch ft {
316+
case "parse-only":
317+
cmd.parseOnly = true
318+
default:
319+
c.reportIssue(fmt.Errorf("unknown or unsupported flag: %q", ft))
320+
return
321+
}
322+
}
323+
c.cmd = cmd
312324
}
313325

314326
func (c *commandParseListener) ExitFnDecl(ctx *parser.FnDeclContext) {

repl/commands_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,10 @@ func TestParse(t *testing.T) {
203203
%exit`),
204204
},
205205
{
206-
commandLine: `%arbitrary --flag -FLAG 'string literal\n'`,
206+
commandLine: `%arbitrary --flag --another-flag 'string literal\n'`,
207207
wantCmd: &simpleCmd{cmd: "arbitrary",
208208
args: []string{
209-
"--flag", "--flag", "string literal\\n",
209+
"--flag", "--another-flag", "string literal\\n",
210210
},
211211
},
212212
},
@@ -284,6 +284,13 @@ func TestParse(t *testing.T) {
284284
src: "",
285285
},
286286
},
287+
{
288+
commandLine: `%eval --parse-only -- foo`,
289+
wantCmd: &evalCmd{
290+
parseOnly: true,
291+
expr: `foo`,
292+
},
293+
},
287294
}
288295

289296
for _, tc := range testCases {
@@ -344,6 +351,24 @@ func TestParseErrors(t *testing.T) {
344351
// not an identifier
345352
commandLine: "%delete 123",
346353
},
354+
{
355+
commandLine: "%eval --badflag foo",
356+
},
357+
{
358+
commandLine: "%eval --bad-flag foo",
359+
},
360+
{
361+
commandLine: "%eval -badflag foo",
362+
},
363+
{
364+
commandLine: "%eval -bad-flag foo",
365+
},
366+
{
367+
commandLine: "%eval --parse-only foo",
368+
},
369+
{
370+
commandLine: "%eval -parse-only foo",
371+
},
347372
}
348373
for _, tc := range testCases {
349374
_, err := Parse(tc.commandLine)

repl/evaluator.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,16 +1302,28 @@ func (e *Evaluator) Process(cmd Cmder) (string, bool, error) {
13021302
}
13031303
return prototext.Format(pAST), false, nil
13041304
case *evalCmd:
1305-
val, resultT, err := e.Evaluate(cmd.expr)
1305+
var (
1306+
val ref.Val
1307+
resultT *exprpb.Type
1308+
err error
1309+
)
1310+
if cmd.parseOnly {
1311+
val, resultT, err = e.EvaluateParseOnly(cmd.expr)
1312+
} else {
1313+
val, resultT, err = e.Evaluate(cmd.expr)
1314+
}
13061315
if err != nil {
13071316
return "", false, fmt.Errorf("expr failed:\n%v", err)
13081317
}
13091318
if val != nil {
1310-
t := UnparseType(resultT)
13111319
unknown, ok := val.Value().(*types.Unknown)
13121320
if ok {
13131321
return fmt.Sprintf("Unknown %v", unknown), false, nil
13141322
}
1323+
if cmd.parseOnly {
1324+
return fmt.Sprintf("%s", types.Format(val)), false, nil
1325+
}
1326+
t := UnparseType(resultT)
13151327
return fmt.Sprintf("%s : %s", types.Format(val), t), false, nil
13161328
}
13171329
case *letVarCmd:
@@ -1394,6 +1406,29 @@ func (e *Evaluator) Evaluate(expr string) (ref.Val, *exprpb.Type, error) {
13941406
return val, ast.ResultType(), err
13951407
}
13961408

1409+
// EvaluateParseOnly evalutes the CEL expression using the current REPL context without type checking.
1410+
func (e *Evaluator) EvaluateParseOnly(expr string) (ref.Val, *exprpb.Type, error) {
1411+
env, act, err := e.applyContext()
1412+
if err != nil {
1413+
return nil, nil, err
1414+
}
1415+
1416+
ast, iss := env.Parse(expr)
1417+
if iss.Err() != nil {
1418+
return nil, nil, iss.Err()
1419+
}
1420+
1421+
p, err := env.Program(ast, e.ctx.programOptions()...)
1422+
if err != nil {
1423+
return nil, nil, err
1424+
}
1425+
1426+
act, _ = env.PartialVars(act)
1427+
val, _, err := p.Eval(act)
1428+
// expression can be well-formed and result in an error
1429+
return val, ast.ResultType(), err
1430+
}
1431+
13971432
// Compile compiles the input expression using the current REPL context.
13981433
func (e *Evaluator) Compile(expr string) (*cel.Ast, error) {
13991434
env, _, err := e.applyContext()

repl/parser/Commands.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ compile: '%compile' e=expr;
5151

5252
parse: '%parse' e=expr;
5353

54-
exprCmd: '%eval'? e=expr;
54+
exprCmd: ('%eval' (flags+=FLAG (flags+=FLAG)* '--')?)? e=expr;
5555

5656
qualId: leadingDot='.'? rid=IDENTIFIER ('.' qualifiers+=IDENTIFIER)*;
5757

@@ -69,6 +69,6 @@ typeParamList:
6969

7070
// lexer rules:
7171
COMMAND: '%' IDENTIFIER;
72-
FLAG: '-'? '-' IDENTIFIER;
72+
FLAG: '-' ('-' IDENTIFIER)+;
7373
ARROW: '->';
7474
EQUAL_ASSIGN: '=';

repl/parser/Commands.interp

Lines changed: 3 additions & 1 deletion
Large diffs are not rendered by default.

repl/parser/Commands.tokens

Lines changed: 73 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,48 @@ T__4=5
66
T__5=6
77
T__6=7
88
T__7=8
9-
COMMAND=9
10-
FLAG=10
11-
ARROW=11
12-
EQUAL_ASSIGN=12
13-
EQUALS=13
14-
NOT_EQUALS=14
15-
IN=15
16-
LESS=16
17-
LESS_EQUALS=17
18-
GREATER_EQUALS=18
19-
GREATER=19
20-
LOGICAL_AND=20
21-
LOGICAL_OR=21
22-
LBRACKET=22
23-
RPRACKET=23
24-
LBRACE=24
25-
RBRACE=25
26-
LPAREN=26
27-
RPAREN=27
28-
DOT=28
29-
COMMA=29
30-
MINUS=30
31-
EXCLAM=31
32-
QUESTIONMARK=32
33-
COLON=33
34-
PLUS=34
35-
STAR=35
36-
SLASH=36
37-
PERCENT=37
38-
CEL_TRUE=38
39-
CEL_FALSE=39
40-
NUL=40
41-
WHITESPACE=41
42-
COMMENT=42
43-
NUM_FLOAT=43
44-
NUM_INT=44
45-
NUM_UINT=45
46-
STRING=46
47-
BYTES=47
48-
IDENTIFIER=48
49-
ESC_IDENTIFIER=49
9+
T__8=9
10+
COMMAND=10
11+
FLAG=11
12+
ARROW=12
13+
EQUAL_ASSIGN=13
14+
EQUALS=14
15+
NOT_EQUALS=15
16+
IN=16
17+
LESS=17
18+
LESS_EQUALS=18
19+
GREATER_EQUALS=19
20+
GREATER=20
21+
LOGICAL_AND=21
22+
LOGICAL_OR=22
23+
LBRACKET=23
24+
RPRACKET=24
25+
LBRACE=25
26+
RBRACE=26
27+
LPAREN=27
28+
RPAREN=28
29+
DOT=29
30+
COMMA=30
31+
MINUS=31
32+
EXCLAM=32
33+
QUESTIONMARK=33
34+
COLON=34
35+
PLUS=35
36+
STAR=36
37+
SLASH=37
38+
PERCENT=38
39+
CEL_TRUE=39
40+
CEL_FALSE=40
41+
NUL=41
42+
WHITESPACE=42
43+
COMMENT=43
44+
NUM_FLOAT=44
45+
NUM_INT=45
46+
NUM_UINT=46
47+
STRING=47
48+
BYTES=48
49+
IDENTIFIER=49
50+
ESC_IDENTIFIER=50
5051
'%help'=1
5152
'%?'=2
5253
'%let'=3
@@ -55,33 +56,34 @@ ESC_IDENTIFIER=49
5556
'%compile'=6
5657
'%parse'=7
5758
'%eval'=8
58-
'->'=11
59-
'='=12
60-
'=='=13
61-
'!='=14
62-
'in'=15
63-
'<'=16
64-
'<='=17
65-
'>='=18
66-
'>'=19
67-
'&&'=20
68-
'||'=21
69-
'['=22
70-
']'=23
71-
'{'=24
72-
'}'=25
73-
'('=26
74-
')'=27
75-
'.'=28
76-
','=29
77-
'-'=30
78-
'!'=31
79-
'?'=32
80-
':'=33
81-
'+'=34
82-
'*'=35
83-
'/'=36
84-
'%'=37
85-
'true'=38
86-
'false'=39
87-
'null'=40
59+
'--'=9
60+
'->'=12
61+
'='=13
62+
'=='=14
63+
'!='=15
64+
'in'=16
65+
'<'=17
66+
'<='=18
67+
'>='=19
68+
'>'=20
69+
'&&'=21
70+
'||'=22
71+
'['=23
72+
']'=24
73+
'{'=25
74+
'}'=26
75+
'('=27
76+
')'=28
77+
'.'=29
78+
','=30
79+
'-'=31
80+
'!'=32
81+
'?'=33
82+
':'=34
83+
'+'=35
84+
'*'=36
85+
'/'=37
86+
'%'=38
87+
'true'=39
88+
'false'=40
89+
'null'=41

repl/parser/CommandsLexer.interp

Lines changed: 4 additions & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)