categraf/inputs/mtail/internal/runtime/compiler/codegen/codegen_test.go

1139 lines
21 KiB
Go

// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.
package codegen_test
import (
"flag"
"strings"
"testing"
"time"
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/code"
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/ast"
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/checker"
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/codegen"
"flashcat.cloud/categraf/inputs/mtail/internal/runtime/compiler/parser"
"flashcat.cloud/categraf/inputs/mtail/internal/testutil"
)
var codegenTestDebug = flag.Bool("codegen_test_debug", false, "Log ASTs and debugging information ")
var testCodeGenPrograms = []struct {
name string
source string
prog []code.Instr // expected bytecode
}{
// Composite literals require too many explicit conversions.
{
"simple line counter",
"counter lines_total\n/$/ { lines_total++\n }\n",
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 7, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 1},
{code.Dload, 0, 1},
{code.Inc, nil, 1},
{code.Setmatched, true, 1},
},
},
{
"count a",
"counter a_count\n/a$/ { a_count++\n }\n",
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 7, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 1},
{code.Dload, 0, 1},
{code.Inc, nil, 1},
{code.Setmatched, true, 1},
},
},
{
"strptime and capref",
"counter foo\n" +
"/(.*)/ { strptime($1, \"2006-01-02T15:04:05\")\n" +
"foo++\n}\n",
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 11, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 1},
{code.Capref, 1, 1},
{code.Str, 0, 1},
{code.Strptime, 2, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"strptime and named capref",
"counter foo\n" +
"/(?P<date>.*)/ { strptime($date, \"2006-01-02T15:04:05\")\n" +
"foo++\n }\n",
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 11, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 1},
{code.Capref, 1, 1},
{code.Str, 0, 1},
{code.Strptime, 2, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"inc by and set",
"counter foo\ncounter bar\n" +
"/([0-9]+)/ {\n" +
"foo += $1\n" +
"bar = $1\n" +
"}\n",
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 16, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2i, nil, 3},
{code.Inc, 0, 3},
{code.Mload, 1, 4},
{code.Dload, 0, 4},
{code.Push, 0, 4},
{code.Capref, 1, 4},
{code.S2i, nil, 4},
{code.Iset, nil, 4},
{code.Setmatched, true, 2},
},
},
{
"cond expr gt",
"counter foo\n" +
"1 > 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, 1, 1},
{code.Jnm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"cond expr lt",
"counter foo\n" +
"1 < 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, -1, 1},
{code.Jnm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"cond expr eq",
"counter foo\n" +
"1 == 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, 0, 1},
{code.Jnm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"cond expr le",
"counter foo\n" +
"1 <= 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, 1, 1},
{code.Jm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"cond expr ge",
"counter foo\n" +
"1 >= 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, -1, 1},
{code.Jm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"cond expr ne",
"counter foo\n" +
"1 != 0 {\n" +
" foo++\n" +
"}\n",
[]code.Instr{
{code.Push, int64(1), 1},
{code.Push, int64(0), 1},
{code.Icmp, 0, 1},
{code.Jm, 6, 1},
{code.Push, true, 1},
{code.Jmp, 7, 1},
{code.Push, false, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"nested cond",
"counter foo\n" +
"/(\\d+)/ {\n" +
" $1 <= 1 {\n" +
" foo++\n" +
" }\n" +
"}\n",
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 19, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2i, nil, 2},
{code.Push, int64(1), 2},
{code.Icmp, 1, 2},
{code.Jm, 11, 2},
{code.Push, true, 2},
{code.Jmp, 12, 2},
{code.Push, false, 2},
{code.Jnm, 18, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Inc, nil, 3},
{code.Setmatched, true, 2},
{code.Setmatched, true, 1},
},
},
{
"deco",
"counter foo\n" +
"counter bar\n" +
"def fooWrap {\n" +
" /.*/ {\n" +
" foo++\n" +
" next\n" +
" }\n" +
"}\n" +
"" +
"@fooWrap { bar++\n }\n",
[]code.Instr{
{code.Match, 0, 3},
{code.Jnm, 10, 3},
{code.Setmatched, false, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Inc, nil, 4},
{code.Mload, 1, 8},
{code.Dload, 0, 8},
{code.Inc, nil, 8},
{code.Setmatched, true, 3},
},
},
{
"length",
"len(\"foo\") > 0 {\n" +
"}\n",
[]code.Instr{
{code.Str, 0, 0},
{code.Length, 1, 0},
{code.Push, int64(0), 0},
{code.Cmp, 1, 0},
{code.Jnm, 7, 0},
{code.Push, true, 0},
{code.Jmp, 8, 0},
{code.Push, false, 0},
{code.Jnm, 11, 0},
{code.Setmatched, false, 0},
{code.Setmatched, true, 0},
},
},
{
"bitwise", `
gauge a
a = 1 & 7 ^ 15 | 8
a = ~ 16 << 2
a = 1 >> 20
`,
[]code.Instr{
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, int64(1), 3},
{code.Push, int64(7), 3},
{code.And, nil, 3},
{code.Push, int64(15), 3},
{code.Xor, nil, 3},
{code.Push, int64(8), 3},
{code.Or, nil, 3},
{code.Iset, nil, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Push, int64(16), 4},
{code.Neg, nil, 4},
{code.Push, int64(2), 4},
{code.Shl, nil, 4},
{code.Iset, nil, 4},
{code.Mload, 0, 5},
{code.Dload, 0, 5},
{code.Push, int64(1), 5},
{code.Push, int64(20), 5},
{code.Shr, nil, 5},
{code.Iset, nil, 5},
},
},
{
"pow", `
gauge a
/(\d+) (\d+)/ {
a = $1 ** $2
}
`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 14, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2i, nil, 3},
{code.Push, 0, 3},
{code.Capref, 2, 3},
{code.S2i, nil, 3},
{code.Ipow, nil, 3},
{code.Iset, nil, 3},
{code.Setmatched, true, 2},
},
},
{
"indexed expr", `
counter a by b
a["string"]++
`,
[]code.Instr{
{code.Str, 0, 2},
{code.Mload, 0, 2},
{code.Dload, 1, 2},
{code.Inc, nil, 2},
},
},
{
"strtol", `
strtol("deadbeef", 16)
`,
[]code.Instr{
{code.Str, 0, 1},
{code.Push, int64(16), 1},
{code.S2i, 2, 1},
},
},
{
"float", `
20.0
`,
[]code.Instr{
{code.Push, 20.0, 1},
},
},
{
"otherwise", `
counter a
otherwise {
a++
}
`,
[]code.Instr{
{code.Otherwise, nil, 2},
{code.Jnm, 7, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Inc, nil, 3},
{code.Setmatched, true, 2},
},
},
{
"cond else",
`counter foo
counter bar
1 > 0 {
foo++
} else {
bar++
}`,
[]code.Instr{
{code.Push, int64(1), 2},
{code.Push, int64(0), 2},
{code.Icmp, 1, 2},
{code.Jnm, 6, 2},
{code.Push, true, 2},
{code.Jmp, 7, 2},
{code.Push, false, 2},
{code.Jnm, 14, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Inc, nil, 3},
{code.Setmatched, true, 2},
{code.Jmp, 17, 2},
{code.Mload, 1, 5},
{code.Dload, 0, 5},
{code.Inc, nil, 5},
},
},
{
"mod",
`
gauge a
a = 3 % 1
`,
[]code.Instr{
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Push, int64(3), 2},
{code.Push, int64(1), 2},
{code.Imod, nil, 2},
{code.Iset, nil, 2},
},
},
{
"del", `
counter a by b
del a["string"]
`,
[]code.Instr{
{code.Str, 0, 2},
{code.Mload, 0, 2},
{code.Del, 1, 2},
},
},
{
"del after", `
counter a by b
del a["string"] after 1h
`,
[]code.Instr{
{code.Push, time.Hour, 2},
{code.Str, 0, 2},
{code.Mload, 0, 2},
{code.Expire, 1, 2},
},
},
{
"types", `
gauge i
gauge f
/(\d+)/ {
i = $1
}
/(\d+\.\d+)/ {
f = $1
}
`,
[]code.Instr{
{code.Match, 0, 3},
{code.Jnm, 10, 3},
{code.Setmatched, false, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Push, 0, 4},
{code.Capref, 1, 4},
{code.S2i, nil, 4},
{code.Iset, nil, 4},
{code.Setmatched, true, 3},
{code.Match, 1, 6},
{code.Jnm, 20, 6},
{code.Setmatched, false, 6},
{code.Mload, 1, 7},
{code.Dload, 0, 7},
{code.Push, 1, 7},
{code.Capref, 1, 7},
{code.S2f, nil, 7},
{code.Fset, nil, 7},
{code.Setmatched, true, 6},
},
},
{
"getfilename", `
getfilename()
`,
[]code.Instr{
{code.Getfilename, 0, 1},
},
},
{
"dimensioned counter",
`counter c by a,b,c
/(\d) (\d) (\d)/ {
c[$1,$2][$3]++
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 13, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.Push, 0, 2},
{code.Capref, 2, 2},
{code.Push, 0, 2},
{code.Capref, 3, 2},
{code.Mload, 0, 2},
{code.Dload, 3, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"string to int",
`counter c
/(.*)/ {
c = int($1)
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 10, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2i, nil, 2},
{code.Iset, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"int to float",
`counter c
/(\d)/ {
c = float($1)
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 10, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2f, nil, 2},
{code.Fset, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"string to float",
`counter c
/(.*)/ {
c = float($1)
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 10, 1},
{code.Setmatched, false, 1},
{code.Mload, 0, 2},
{code.Dload, 0, 2},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2f, nil, 2},
{code.Fset, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"float to string",
`counter c by a
/(\d+\.\d+)/ {
c[string($1)] ++
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 11, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2f, nil, 2},
{code.F2s, nil, 2},
{code.Mload, 0, 2},
{code.Dload, 1, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"int to string",
`counter c by a
/(\d+)/ {
c[string($1)] ++
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 11, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2i, nil, 2},
{code.I2s, nil, 2},
{code.Mload, 0, 2},
{code.Dload, 1, 2},
{code.Inc, nil, 2},
{code.Setmatched, true, 1},
},
},
{
"nested comparisons",
`counter foo
/(.*)/ {
$1 == "foo" || $1 == "bar" {
foo++
}
}
`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 31, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.Str, 0, 2},
{code.Scmp, 0, 2},
{code.Jnm, 10, 2},
{code.Push, true, 2},
{code.Jmp, 11, 2},
{code.Push, false, 2},
{code.Jm, 23, 2},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.Str, 1, 2},
{code.Scmp, 0, 2},
{code.Jnm, 19, 2},
{code.Push, true, 2},
{code.Jmp, 20, 2},
{code.Push, false, 2},
{code.Jm, 23, 2},
{code.Push, false, 2},
{code.Jmp, 24, 2},
{code.Push, true, 2},
{code.Jnm, 30, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Inc, nil, 3},
{code.Setmatched, true, 2},
{code.Setmatched, true, 1},
},
},
{
"string concat", `
counter f by s
/(.*), (.*)/ {
f[$1 + $2]++
}
`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 12, 2},
{code.Setmatched, false, 2},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Push, 0, 3},
{code.Capref, 2, 3},
{code.Cat, nil, 3},
{code.Mload, 0, 3},
{code.Dload, 1, 3},
{code.Inc, nil, 3},
{code.Setmatched, true, 2},
},
},
{
"add assign float", `
gauge foo
/(\d+\.\d+)/ {
foo += $1
}
`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 13, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2f, nil, 3},
{code.Fadd, nil, 3},
{code.Fset, nil, 3},
{code.Setmatched, true, 2},
},
},
{
"match expression", `
counter foo
/(.*)/ {
$1 =~ /asdf/ {
foo++
}
}`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 13, 2},
{code.Setmatched, false, 2},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Smatch, 1, 3},
{code.Jnm, 12, 3},
{code.Setmatched, false, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Inc, nil, 4},
{code.Setmatched, true, 3},
{code.Setmatched, true, 2},
},
},
{
"negative match expression", `
counter foo
/(.*)/ {
$1 !~ /asdf/ {
foo++
}
}`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 14, 2},
{code.Setmatched, false, 2},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Smatch, 1, 3},
{code.Not, nil, 3},
{code.Jnm, 13, 3},
{code.Setmatched, false, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Inc, nil, 4},
{code.Setmatched, true, 3},
{code.Setmatched, true, 2},
},
},
{
"capref used in def", `
/(?P<x>\d+)/ && $x > 5 {
}`,
[]code.Instr{
{code.Match, 0, 1},
{code.Jnm, 14, 1},
{code.Push, 0, 1},
{code.Capref, 1, 1},
{code.S2i, nil, 1},
{code.Push, int64(5), 1},
{code.Icmp, 1, 1},
{code.Jnm, 10, 1},
{code.Push, true, 1},
{code.Jmp, 11, 1},
{code.Push, false, 1},
{code.Jnm, 14, 1},
{code.Push, true, 1},
{code.Jmp, 15, 1},
{code.Push, false, 1},
{code.Jnm, 18, 1},
{code.Setmatched, false, 1},
{code.Setmatched, true, 1},
},
},
{
"binop arith type conversion", `
gauge var
/(?P<x>\d+) (\d+\.\d+)/ {
var = $x + $2
}`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 15, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2i, nil, 3},
{code.I2f, nil, 3},
{code.Push, 0, 3},
{code.Capref, 2, 3},
{code.S2f, nil, 3},
{code.Fadd, nil, 3},
{code.Fset, nil, 3},
{code.Setmatched, true, 2},
},
},
{
"binop compare type conversion", `
counter var
/(?P<x>\d+) (\d+\.\d+)/ {
$x > $2 {
var++
}
}`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 22, 2},
{code.Setmatched, false, 2},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2i, nil, 3},
{code.I2f, nil, 3},
{code.Push, 0, 3},
{code.Capref, 2, 3},
{code.S2f, nil, 3},
{code.Fcmp, 1, 3},
{code.Jnm, 14, 3},
{code.Push, true, 3},
{code.Jmp, 15, 3},
{code.Push, false, 3},
{code.Jnm, 21, 3},
{code.Setmatched, false, 3},
{code.Mload, 0, 4},
{code.Dload, 0, 4},
{code.Inc, nil, 4},
{code.Setmatched, true, 3},
{code.Setmatched, true, 2},
},
},
{"set string", `
text foo
/(.*)/ {
foo = $1
}
`, []code.Instr{
{code.Match, 0, 2},
{code.Jnm, 9, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Sset, nil, 3},
{code.Setmatched, true, 2},
}},
{
"concat to text", `
text foo
/(?P<v>.*)/ {
foo += $v
}`,
[]code.Instr{
{code.Match, 0, 2},
{code.Jnm, 12, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Cat, nil, 3},
{code.Sset, nil, 3},
{code.Setmatched, true, 2},
},
},
{"decrement", `
counter i
// {
i--
}`, []code.Instr{
{code.Match, 0, 2},
{code.Jnm, 7, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Dec, nil, 3},
{code.Setmatched, true, 2},
}},
{"capref and settime", `
/(\d+)/ {
settime($1)
}`, []code.Instr{
{code.Match, 0, 1},
{code.Jnm, 8, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2i, nil, 2},
{code.Settime, 1, 2},
{code.Setmatched, true, 1},
}},
{"cast to self", `
/(\d+)/ {
settime(int($1))
}`, []code.Instr{
{code.Match, 0, 1},
{code.Jnm, 8, 1},
{code.Setmatched, false, 1},
{code.Push, 0, 2},
{code.Capref, 1, 2},
{code.S2i, nil, 2},
{code.Settime, 1, 2},
{code.Setmatched, true, 1},
}},
{"stop", `
stop
`, []code.Instr{
{code.Stop, nil, 1},
}},
{"stop inside", `
// {
stop
}
`, []code.Instr{
{code.Match, 0, 1},
{code.Jnm, 5, 1},
{code.Setmatched, false, 1},
{code.Stop, nil, 2},
{code.Setmatched, true, 1},
}},
{
"nested decorators",
`def b {
def b {
next
}
@b {
next
}
}
@b {
}`, nil,
},
{"negative numbers in capture groups", `
gauge foo
/(?P<value_ms>-?\d+)/ {
foo += $value_ms / 1000.0
}`, []code.Instr{
{code.Match, 0, 2},
{code.Jnm, 16, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.S2i, nil, 3},
{code.I2f, nil, 3},
{code.Push, 1000.0, 3},
{code.Fdiv, nil, 3},
{code.Fadd, nil, 3},
{code.Fset, nil, 3},
{code.Setmatched, true, 2},
}},
{"substitution", `
gauge foo
/(\d+,\d)/ {
foo = int(subst(",", "", $1))
}`, []code.Instr{
{code.Match, 0, 2},
{code.Jnm, 13, 2},
{code.Setmatched, false, 2},
{code.Mload, 0, 3},
{code.Dload, 0, 3},
{code.Str, 0, 3},
{code.Str, 1, 3},
{code.Push, 0, 3},
{code.Capref, 1, 3},
{code.Subst, 3, 3},
{code.S2i, nil, 3},
{code.Iset, nil, 3},
{code.Setmatched, true, 2},
}},
{"const term as pattern", `
const A /n/
A && 1 {
}
`, []code.Instr{
{code.Match, 0, 0},
{code.Jnm, 6, 0},
{code.Push, int64(1), 2},
{code.Jnm, 6, 0},
{code.Push, true, 0},
{code.Jmp, 7, 0},
{code.Push, false, 0},
{code.Jnm, 10, 0},
{code.Setmatched, false, 0},
{code.Setmatched, true, 0},
}},
}
func TestCodeGenFromSource(t *testing.T) {
for _, tc := range testCodeGenPrograms {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ast, err := parser.Parse(tc.name, strings.NewReader(tc.source))
testutil.FatalIfErr(t, err)
ast, err = checker.Check(ast, 0, 0)
if *codegenTestDebug {
s := parser.Sexp{}
s.EmitTypes = true
t.Log("Typed AST:\n" + s.Dump(ast))
}
testutil.FatalIfErr(t, err)
obj, err := codegen.CodeGen(tc.name, ast)
testutil.FatalIfErr(t, err)
testutil.ExpectNoDiff(t, tc.prog, obj.Program, testutil.AllowUnexported(code.Instr{}))
})
}
}
var testCodeGenASTs = []struct {
name string
ast ast.Node // partial AST to be converted to bytecode
prog []code.Instr // expected bytecode
}{
{
name: "subst",
ast: &ast.BuiltinExpr{
Name: "subst",
Args: &ast.ExprList{
Children: []ast.Node{
&ast.StringLit{
Text: "old",
},
&ast.StringLit{
Text: "new",
},
&ast.StringLit{
Text: "value",
},
},
},
},
prog: []code.Instr{
{code.Str, 0, 0},
{code.Str, 1, 0},
{code.Str, 2, 0},
{code.Subst, 3, 0},
},
},
{
name: "regexp subst",
ast: &ast.BuiltinExpr{
Name: "subst",
Args: &ast.ExprList{
Children: []ast.Node{
&ast.PatternExpr{
Pattern: "a+",
Expr: &ast.PatternLit{
Pattern: "a+",
},
},
&ast.StringLit{
Text: "b",
},
&ast.StringLit{
Text: "aaaaaa",
},
},
},
},
prog: []code.Instr{
{code.Str, 0, 0},
{code.Str, 1, 0},
{code.Push, 0, 0},
{code.Rsubst, 3, 0},
},
},
}
func TestCodeGenFromAST(t *testing.T) {
for _, tc := range testCodeGenASTs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
obj, err := codegen.CodeGen(tc.name, tc.ast)
testutil.FatalIfErr(t, err)
testutil.ExpectNoDiff(t, tc.prog, obj.Program, testutil.AllowUnexported(code.Instr{}))
})
}
}