diff --git a/cmd/arrai/bundle.go b/cmd/arrai/bundle.go
index 902764de..5e16b8af 100644
--- a/cmd/arrai/bundle.go
+++ b/cmd/arrai/bundle.go
@@ -6,6 +6,8 @@ import (
"os"
"path/filepath"
+ "github.com/spf13/afero"
+
"github.com/arr-ai/arrai/pkg/arraictx"
"github.com/arr-ai/arrai/pkg/ctxfs"
"github.com/arr-ai/arrai/syntax"
@@ -53,7 +55,7 @@ func bundleFilesTo(ctx context.Context, path string, w io.Writer, out string) (e
w = f
}
- buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path)
+ buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path)
if err != nil {
return err
}
diff --git a/cmd/arrai/compile.go b/cmd/arrai/compile.go
index 124ca052..01892458 100644
--- a/cmd/arrai/compile.go
+++ b/cmd/arrai/compile.go
@@ -151,7 +151,7 @@ func buildBinary(ctx context.Context, bundledScripts []byte, out afero.File) err
}
}
- file, err := ctxfs.ReadFile(fs, filepath.Join(buildDir, "main"))
+ file, err := afero.ReadFile(fs, filepath.Join(buildDir, "main"))
if err != nil {
return err
}
diff --git a/cmd/arrai/run.go b/cmd/arrai/run.go
index ff7aab57..d4ef64bc 100644
--- a/cmd/arrai/run.go
+++ b/cmd/arrai/run.go
@@ -8,6 +8,8 @@ import (
"path/filepath"
"strings"
+ "github.com/spf13/afero"
+
"github.com/arr-ai/arrai/pkg/arrai"
"github.com/arr-ai/arrai/pkg/arraictx"
"github.com/arr-ai/arrai/pkg/ctxfs"
@@ -37,7 +39,7 @@ func evalFile(ctx context.Context, path string, w io.Writer, out string) error {
if err := fileExists(ctx, path); err != nil {
return err
}
- buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path)
+ buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path)
if err != nil {
return err
}
diff --git a/cmd/arrai/sync_other.go b/cmd/arrai/sync_other.go
index 05983fa0..8112a2f3 100644
--- a/cmd/arrai/sync_other.go
+++ b/cmd/arrai/sync_other.go
@@ -21,6 +21,7 @@ import (
"github.com/go-errors/errors"
"github.com/rjeczalik/notify"
log "github.com/sirupsen/logrus"
+ "github.com/spf13/afero"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
)
@@ -123,7 +124,7 @@ func buildTree(ctx context.Context, root string) (map[string]interface{}, error)
if err != nil {
log.Printf("ERROR WALKING TO %s: %s", path, err)
} else if !info.IsDir() {
- data, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), path)
+ data, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), path)
if err != nil {
return errors.WrapPrefix(err, "reading "+path, 0)
}
diff --git a/cmd/arrai/test.go b/cmd/arrai/test.go
index 5b279388..d1edac34 100644
--- a/cmd/arrai/test.go
+++ b/cmd/arrai/test.go
@@ -2,10 +2,7 @@ package main
import (
"context"
- "fmt"
- "io"
"os"
- "strings"
"github.com/arr-ai/arrai/internal/test"
"github.com/arr-ai/arrai/pkg/arraictx"
@@ -20,32 +17,8 @@ var testCommand = &cli.Command{
}
func doTest(c *cli.Context) error {
- file := c.Args().Get(0)
+ path := c.Args().Get(0)
ctx := arraictx.InitCliCtx(context.Background(), c)
- return testPath(ctx, file, os.Stdout)
-}
-
-// testPath runs and reports on all tests in the subtree of the given path.
-func testPath(ctx context.Context, path string, w io.Writer) error {
- if path == "" {
- path = "."
- }
- if _, err := os.Stat(path); os.IsNotExist(err) {
- if !strings.Contains(path, string([]rune{os.PathSeparator})) {
- return fmt.Errorf(`"%s": not a command and not found as a file in the current directory`, path)
- }
- return fmt.Errorf(`"%s": file not found`, path)
- }
-
- results, err := test.Test(ctx, w, path)
- if err != nil {
- return err
- }
-
- err = test.Report(w, results)
- if err != nil {
- return err
- }
- return nil
+ return test.RunTests(ctx, os.Stdout, path)
}
diff --git a/cmd/arrai/test_test.go b/cmd/arrai/test_test.go
deleted file mode 100644
index a3b7d72b..00000000
--- a/cmd/arrai/test_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package main
-
-import (
- "context"
- "strings"
- "testing"
-
- "github.com/arr-ai/arrai/pkg/arraictx"
- "github.com/stretchr/testify/require"
-)
-
-func TestTest(t *testing.T) {
- t.Parallel()
-
- var s strings.Builder
- err := testPath(arraictx.InitRunCtx(context.Background()), "./../../examples/test", &s)
- require.Nil(t, err)
- windowsOsStr := `Tests:
-..\..\examples\test\multiple_cases_test.arrai
-..\..\examples\test\single_case_test.arrai
-all tests passed
-`
- linuxOsStr := `Tests:
-../../examples/test/multiple_cases_test.arrai
-../../examples/test/single_case_test.arrai
-all tests passed
-`
- require.True(t, (windowsOsStr == s.String()) || (linuxOsStr == s.String()))
-}
diff --git a/contrib/table_test.arrai b/contrib/table_test.arrai
index 48cb302f..ada96285 100644
--- a/contrib/table_test.arrai
+++ b/contrib/table_test.arrai
@@ -10,14 +10,16 @@ let multipleHtml =
b | [1, 2, 3] |
';
+let remWhitespace = \s //re.compile('\\s').sub('', s);
+
(
markdown: (
- empty: markdown({}) = '|Key|Value|\n',
- single: markdown(single) = '|Key|Value|\n|a|1|',
- multiple: markdown(multiple) = '|Key|Value|\n|a|1|\n|b|[1, 2, 3]|',
+ empty: remWhitespace(markdown({})) = '|Key|Value|',
+ single: remWhitespace(markdown(single)) = '|Key|Value||a|1|',
+ multiple: remWhitespace(markdown(multiple)) = '|Key|Value||a|1||b|[1,2,3]|',
),
html: (
- empty: html({}) = '',
- multiple: html(multiple) = multipleHtml,
+ empty: remWhitespace(html({})) = '',
+ multiple: remWhitespace(html(multiple)) = remWhitespace(multipleHtml),
),
)
diff --git a/examples/test/README.md b/examples/test/README.md
index 2b411871..f8a4f994 100644
--- a/examples/test/README.md
+++ b/examples/test/README.md
@@ -1,5 +1,5 @@
# Test case examples
-This directory has the following examples of arr.ai test cases:
+This directory has the following examples of arr.ai test cases which uses the new leaf-true structure:
- **[Single test case in one test file](single_case_test.arrai)**: Only one test case in an arr.ai test file.
- **[Multiple test cases in one test file](multiple_cases_test.arrai):** Multiple test cases in an arr.ai test file.
diff --git a/examples/test/multiple_cases_test.arrai b/examples/test/multiple_cases_test.arrai
index 4f91e431..a48dc4e0 100644
--- a/examples/test/multiple_cases_test.arrai
+++ b/examples/test/multiple_cases_test.arrai
@@ -1,9 +1,31 @@
-# 5 test cases
+let str = "Hello, arrai!";
(
- # Added expression let to clarify the actual result can be a complex expression
- testCase1: //test.assert.equal("Hello, arrai!", let words = ["Hello", "arrai!"];//seq.join(", ", words)),
- testCase2: //test.assert.unequal("Hello, arrai!", //seq.join(", ", ["Nihao", "arrai!"])),
- testCase3: //test.assert.size(2, {"Hello", "arrai!"}),
- testCase4: //test.assert.true({"Hello", "arrai!"}),
- testCase5: //test.assert.false({}),
+ tuple:
+ (
+ testCase1: str = let words = ["Hello", "arrai!"];//seq.join(", ", words),
+ testCase2: str != //seq.join(", ", ["Nihao", "arrai!"]),
+ # notATest1: 5,
+ # notATest2: { 1 = 1, 2 = 1 },
+ #_ignoredTest: 1 = 2,
+ ),
+ #_ignoredContainer:
+ #(
+ # testCase1: 1 = 2,
+ # _testCase2: 1 = 2,
+ #),
+ testCase3: 2 = {"Hello", "arrai!"} count,
+ dict:
+ {
+ # "testCase4": 4 = 7,
+ "testCase5": (a: 5 = 5, b: 6 = 6),
+ },
+ array:
+ [
+ 7 = 7,
+ [8 = 8, (a: 9 = 9, b: 10 = 10)],
+ let value = {str}; cond {value: true} = true,
+ let value = {}; cond {value: true} = false,
+ #true = false,
+ ],
+ deep: (deeper: (deepest: 9 = 9)),
)
diff --git a/examples/test/single_case_test.arrai b/examples/test/single_case_test.arrai
index 7cde448a..db4ace17 100644
--- a/examples/test/single_case_test.arrai
+++ b/examples/test/single_case_test.arrai
@@ -1 +1 @@
-//test.assert.equal("Hello", //str.title("hello"))
+"Hello" = //str.title("hello")
diff --git a/examples/test_old/README.md b/examples/test_old/README.md
new file mode 100644
index 00000000..5937642c
--- /dev/null
+++ b/examples/test_old/README.md
@@ -0,0 +1,5 @@
+# Test case examples
+
+This directory has the following examples of arr.ai test cases using the obsolete assert functions:
+- **[Single test case in one test file](single_case_test.arrai)**: Only one test case in an arr.ai test file.
+- **[Multiple test cases in one test file](multiple_cases_test.arrai):** Multiple test cases in an arr.ai test file.
diff --git a/examples/test_old/multiple_cases_test.arrai b/examples/test_old/multiple_cases_test.arrai
new file mode 100644
index 00000000..4f91e431
--- /dev/null
+++ b/examples/test_old/multiple_cases_test.arrai
@@ -0,0 +1,9 @@
+# 5 test cases
+(
+ # Added expression let to clarify the actual result can be a complex expression
+ testCase1: //test.assert.equal("Hello, arrai!", let words = ["Hello", "arrai!"];//seq.join(", ", words)),
+ testCase2: //test.assert.unequal("Hello, arrai!", //seq.join(", ", ["Nihao", "arrai!"])),
+ testCase3: //test.assert.size(2, {"Hello", "arrai!"}),
+ testCase4: //test.assert.true({"Hello", "arrai!"}),
+ testCase5: //test.assert.false({}),
+)
diff --git a/examples/test_old/single_case_test.arrai b/examples/test_old/single_case_test.arrai
new file mode 100644
index 00000000..7cde448a
--- /dev/null
+++ b/examples/test_old/single_case_test.arrai
@@ -0,0 +1 @@
+//test.assert.equal("Hello", //str.title("hello"))
diff --git a/go.mod b/go.mod
index b061a68f..28f2342d 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
+ golang.org/x/text v0.3.3
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/grpc v1.31.1
google.golang.org/protobuf v1.25.0
diff --git a/internal/test/entities.go b/internal/test/entities.go
new file mode 100644
index 00000000..dd824e63
--- /dev/null
+++ b/internal/test/entities.go
@@ -0,0 +1,25 @@
+package test
+
+import "time"
+
+type testFile struct {
+ path string
+ source string
+ wallTime time.Duration
+ results []testResult
+}
+
+type testResult struct {
+ name string
+ outcome testOutcome
+ message string
+}
+
+type testOutcome int
+
+const (
+ Failed testOutcome = iota
+ Invalid
+ Ignored
+ Passed
+)
diff --git a/internal/test/report.go b/internal/test/report.go
index c8c43efa..88a5d7dc 100644
--- a/internal/test/report.go
+++ b/internal/test/report.go
@@ -3,28 +3,109 @@ package test
import (
"fmt"
"io"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
)
-// Report prints the results of the testing to the IO writer.
-func Report(w io.Writer, rs Results) error {
- if rs.AllPassed() {
- _, err := fmt.Fprintln(w, "all tests passed")
- if err != nil {
- return err
- }
- } else {
- for _, r := range rs.results {
- if !r.pass {
- _, err := fmt.Fprintf(w, "%s failed\n", r.file)
- if err != nil {
- return err
- }
+// Report writes a formatted output of all the test files and their test results, and returns and error if the test run
+// failed.
+func Report(w io.Writer, testFiles []testFile) error {
+ stats := calcStats(testFiles)
+
+ for _, testFile := range testFiles {
+ reportFile(w, testFile, stats.maxNameLen)
+ }
+
+ reportStats(w, stats)
+
+ if stats.runFailed {
+ return fmt.Errorf("test run "+color, 41, "FAILED")
+ }
+
+ return nil
+}
+
+const color = "\033[38;5;255;%d;1m%s\033[0m"
+
+// reportFile writes a formatted output of a file and all its test results, ordered by outcome. The maxName parameter
+// is used to aligned the results, and should contain the length of the longest testResult.name inside testFile.results.
+func reportFile(w io.Writer, testFile testFile, maxName int) {
+ results := testFile.results
+
+ // Sort test results by outcome then by test name
+ sort.Slice(results, func(i, j int) bool {
+ left, right := results[i], results[j]
+ if left.outcome == right.outcome {
+ if left.name == right.name {
+ return true
}
+ return left.name < right.name
}
- _, err := fmt.Fprintf(w, "%d/%d tests passed\n", rs.CountPassed(), rs.Count())
- if err != nil {
- return err
- }
+ return left.outcome > right.outcome
+ })
+
+ message.NewPrinter(language.English).
+ Fprintf(w, "\n======= %s (%dms)\n", relPath(testFile.path), testFile.wallTime.Milliseconds())
+ for _, result := range results {
+ reportTest(w, result, maxName)
}
- return nil
+}
+
+// relPath makes a best-effort attempt to compute the relative path of the given absolute path. If it fails, it returns
+// the absolute path untouched.
+func relPath(absPath string) string {
+ cwd, wdErr := os.Getwd()
+ relPath, relErr := filepath.Rel(cwd, absPath)
+
+ if wdErr != nil || relErr != nil {
+ return absPath
+ }
+
+ return relPath
+}
+
+// reportTest writes a formatted output of a single test result (PASS/FAIL/SKIP/??) with the optional included message.
+func reportTest(w io.Writer, test testResult, maxName int) {
+ switch test.outcome {
+ case Failed:
+ fmt.Fprintf(w, color, 41, "FAIL")
+ case Invalid:
+ fmt.Fprintf(w, color, 31, " ?? ")
+ case Ignored:
+ fmt.Fprintf(w, color, 93, "SKIP")
+ case Passed:
+ fmt.Fprintf(w, color, 32, "PASS")
+ }
+
+ fmt.Fprintf(w, " %s\n", test.name+strings.Repeat(" ", maxName-utf8.RuneCountInString(test.name)))
+
+ if test.message != "" {
+ fmt.Fprintf(w, " %s\n", strings.ReplaceAll(test.message, "\n", "\n "))
+ }
+}
+
+// reportStats writes a formatted single line representation of the aggregated test statistics.
+func reportStats(w io.Writer, stats testStats) {
+ p := message.NewPrinter(language.English)
+ p.Fprintf(w, "\n======= Summary\n")
+
+ if stats.failed > 0 {
+ p.Fprintf(w, "%d failed, ", stats.failed)
+ }
+
+ if stats.invalid > 0 {
+ p.Fprintf(w, "%d invalid, ", stats.invalid)
+ }
+
+ if stats.ignored > 0 {
+ p.Fprintf(w, "%d ignored, ", stats.ignored)
+ }
+
+ p.Fprintf(w, "%d passed of %d total tests. Took %dms.\n", stats.passed, stats.total, stats.wallTime.Milliseconds())
}
diff --git a/internal/test/report_test.go b/internal/test/report_test.go
new file mode 100644
index 00000000..a8c5b611
--- /dev/null
+++ b/internal/test/report_test.go
@@ -0,0 +1,151 @@
+package test
+
+import (
+ "bytes"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+// ===== report
+
+func TestReport_TwoFilesPass(t *testing.T) {
+ t.Parallel()
+
+ err := reportShouldContain(t, generateFiles(),
+ "path/to/file1.arrai", "1,234ms", "PASS", "testA",
+ "path/to/file2.arrai", "4,321ms", "PASS", "testB",
+ "2 passed", "2 total", "5,555ms")
+ require.NoError(t, err)
+}
+
+func TestReport_TwoFilesFail(t *testing.T) {
+ t.Parallel()
+
+ files := generateFiles()
+ files[1].results[0] = testResult{name: "testB", outcome: Failed, message: "Uh oh"}
+ err := reportShouldContain(t, files,
+ "path/to/file1.arrai", "1,234ms", "PASS", "testA",
+ "path/to/file2.arrai", "4,321ms", "FAIL", "testB",
+ "1 failed", "1 passed", "2 total", "5,555ms")
+ require.Error(t, err)
+}
+
+// ===== reportFile
+
+func TestReportFile_WithTest(t *testing.T) {
+ t.Parallel()
+ generateFiles()[0].reportShouldContain(t, "path/to/file1.arrai", "1,234ms", "PASS", "testA")
+}
+
+func TestReportFile_SortsCorrectly(t *testing.T) {
+ t.Parallel()
+ file := generateFiles()[0]
+ file.results = []testResult{
+ {name: "failedB", outcome: Failed},
+ {name: "failedA", outcome: Failed},
+ {name: "ignoredB", outcome: Ignored},
+ {name: "ignoredA", outcome: Ignored},
+ {name: "invalidB", outcome: Invalid},
+ {name: "invalidA", outcome: Invalid},
+ {name: "passedA", outcome: Passed},
+ {name: "passedA", outcome: Passed, message: "Duplicate"},
+ }
+ file.reportShouldContain(t, "passedA", "Duplicate", "ignoredA", "ignoredB", "invalidA", "invalidB",
+ "failedA", "failedB")
+}
+
+// ===== reportTest
+
+func TestReportTest_Passed(t *testing.T) {
+ t.Parallel()
+ testResult{name: "test1", outcome: Passed}.
+ reportShouldContain(t, "PASS", "test1")
+}
+
+func TestReportTest_Failed(t *testing.T) {
+ t.Parallel()
+ testResult{name: "test2", outcome: Failed, message: "Failed!"}.
+ reportShouldContain(t, "FAIL", "test2", "Failed!")
+}
+
+func TestReportTest_Invalid(t *testing.T) {
+ t.Parallel()
+ testResult{name: "test3", outcome: Invalid, message: "Invalid!"}.
+ reportShouldContain(t, "??", "test3", "Invalid!")
+}
+
+func TestReportTest_Ignored(t *testing.T) {
+ t.Parallel()
+ testResult{name: "test4", outcome: Ignored}.
+ reportShouldContain(t, "SKIP", "test4")
+}
+
+// ===== reportStats
+
+func TestReportStats_Passed(t *testing.T) {
+ t.Parallel()
+ testStats{wallTime: time.Millisecond * 11111, total: 2, passed: 2}.
+ reportShouldContain(t, "2 passed", "2 total", "11,111ms")
+}
+
+func TestReportStats_Failed(t *testing.T) {
+ t.Parallel()
+ testStats{wallTime: time.Millisecond * 888, total: 4, passed: 1, failed: 1, invalid: 1, ignored: 1}.
+ reportShouldContain(t, "1 failed", "1 invalid", "1 ignored", "1 passed", "4 total", "888ms")
+}
+
+// ===== Helpers
+
+func reportShouldContain(t *testing.T, files []testFile, keyPhrases ...string) error {
+ buf := bytes.Buffer{}
+ err := Report(&buf, files)
+ shouldContain(t, buf.String(), keyPhrases...)
+ return err
+}
+
+func (file testFile) reportShouldContain(t *testing.T, keyPhrases ...string) {
+ buf := bytes.Buffer{}
+ reportFile(&buf, file, 10)
+ shouldContain(t, buf.String(), keyPhrases...)
+}
+
+func (test testResult) reportShouldContain(t *testing.T, keyPhrases ...string) {
+ buf := bytes.Buffer{}
+ reportTest(&buf, test, 10)
+ shouldContain(t, buf.String(), keyPhrases...)
+}
+
+func (stats testStats) reportShouldContain(t *testing.T, keyPhrases ...string) {
+ buf := bytes.Buffer{}
+ reportStats(&buf, stats)
+ shouldContain(t, buf.String(), keyPhrases...)
+}
+
+// shouldContain verifies that the provided keyPhrases exist in str in the order specified.
+func shouldContain(t *testing.T, str string, keyPhrases ...string) {
+ for _, phrase := range keyPhrases {
+ index := strings.Index(str, phrase)
+ if index == -1 {
+ require.Failf(t, "shouldContain() failed",
+ "Key phrase '%s' was not found in remaining unmatched string: %s", strconv.Quote(phrase), strconv.Quote(str))
+ }
+ str = str[index+len(phrase):]
+ }
+}
+
+func generateFiles() []testFile {
+ return []testFile{
+ {
+ path: "/path/to/file1.arrai", wallTime: 1234 * time.Millisecond,
+ results: []testResult{{name: "testA", outcome: Passed}},
+ },
+ {
+ path: "/path/to/file2.arrai", wallTime: 4321 * time.Millisecond,
+ results: []testResult{{name: "testB", outcome: Passed}},
+ },
+ }
+}
diff --git a/internal/test/results.go b/internal/test/results.go
deleted file mode 100644
index 5cfa315c..00000000
--- a/internal/test/results.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package test
-
-// Result describes the outcome of a particular test case.
-type Result struct {
- file string
- pass bool
-}
-
-// Results is a collection of test results from a test suite.
-type Results struct {
- results []Result
-}
-
-// Add adds a result to the set of results.
-func (rs *Results) Add(r Result) {
- rs.results = append(rs.results, r)
-}
-
-// AllPassed returns true if all results were successful.
-func (rs Results) AllPassed() bool {
- for _, r := range rs.results {
- if !r.pass {
- return false
- }
- }
- return true
-}
-
-// Count returns the number of results in the set.
-func (rs Results) Count() int {
- return len(rs.results)
-}
-
-// CountFailed returns the number of tests that failed.
-func (rs Results) CountFailed() int {
- count := 0
- for _, r := range rs.results {
- if !r.pass {
- count++
- }
- }
- return count
-}
-
-// CountPassed returns the number of tests that passed.
-func (rs Results) CountPassed() int {
- count := 0
- for _, r := range rs.results {
- if r.pass {
- count++
- }
- }
- return count
-}
diff --git a/internal/test/runner.go b/internal/test/runner.go
new file mode 100644
index 00000000..01559293
--- /dev/null
+++ b/internal/test/runner.go
@@ -0,0 +1,127 @@
+package test
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/spf13/afero"
+
+ "github.com/arr-ai/arrai/pkg/ctxfs"
+ "github.com/arr-ai/arrai/rel"
+ "github.com/arr-ai/arrai/syntax"
+)
+
+// RunTests runs all arr.ai tests in a given path. It returns an error if the path is invalid, contains no test files or
+// has invalid arr.ai code in any of them.
+func RunTests(ctx context.Context, w io.Writer, path string) error {
+ if path == "" || path == "." {
+ var err error
+ path, err = os.Getwd()
+ if err != nil {
+ return err
+ }
+ }
+
+ files, err := getTestFiles(ctx, path)
+ if err != nil {
+ return err
+ }
+
+ for i := range files {
+ err = runFile(ctx, &files[i])
+ if err != nil {
+ return err
+ }
+ }
+
+ err = Report(w, files)
+ return err
+}
+
+// getTestFiles finds all *_test.arrai files in given path (recursively), reads them and returns a testFile array with
+// them. It skips over hidden directories. It returns an error if any filesystem operation failed, or if no files were
+// found.
+func getTestFiles(ctx context.Context, path string) ([]testFile, error) {
+ var files []testFile
+ fs := ctxfs.SourceFsFrom(ctx)
+
+ err := afero.Walk(fs, path, func(path string, info os.FileInfo, walkErr error) error {
+ if walkErr != nil {
+ return walkErr
+ }
+
+ if info.IsDir() {
+ // Skip hidden dirs.
+ if strings.HasPrefix(info.Name(), ".") {
+ return filepath.SkipDir
+ }
+
+ // Not a file, continue walking the directory tree.
+ return nil
+ }
+
+ if strings.HasSuffix(path, "_test.arrai") == false { //nolint:gosimple
+ return nil
+ }
+
+ bytes, readErr := afero.ReadFile(fs, path)
+ if readErr != nil {
+ return fmt.Errorf("failed reading test file '%s': %v", path, readErr)
+ }
+
+ files = append(files, testFile{path: path, source: string(bytes)})
+ return nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ if len(files) == 0 {
+ return nil, fmt.Errorf("no test files (ending in '_test.arrai') were found in path: %v", path)
+ }
+ return files, nil
+}
+
+// runFile runs all tests in testFile.source and fills testFile.results and testFile.wallTime. It returns an error if
+// the arr.ai code failed to evaluate.
+func runFile(ctx context.Context, file *testFile) error {
+ start := time.Now()
+ result, err := syntax.EvaluateExpr(ctx, file.path, file.source)
+ file.wallTime = time.Since(start)
+
+ if err != nil {
+ return fmt.Errorf("failed evaluating tests file '%s': %v", file.path, err)
+ }
+
+ file.results = make([]testResult, 0)
+ ForeachLeaf(result, "", func(val rel.Value, path string) {
+ result := testResult{
+ name: path,
+ }
+
+ if isLiteralTrue(val) {
+ result.outcome = Passed
+ } else if isLiteralFalse(val) {
+ result.outcome = Failed
+ result.message = "Expected: true. Actual: false."
+ } else {
+ result.outcome = Invalid
+ result.message = fmt.Sprintf("Could not determine test outcome due to non-boolean result of type %s: %s",
+ rel.ValueTypeAsString(val), val.String())
+
+ if _, ok := val.(rel.GenericSet); ok {
+ result.message = fmt.Sprintf("Sets are not allowed as test containers. Please use tuples, " +
+ "dictionaries or arrays.")
+ }
+ }
+
+ file.results = append(file.results, result)
+ })
+
+ return nil
+}
diff --git a/internal/test/runner_test.go b/internal/test/runner_test.go
new file mode 100644
index 00000000..099e5575
--- /dev/null
+++ b/internal/test/runner_test.go
@@ -0,0 +1,221 @@
+// +build !windows
+
+package test
+
+import (
+ "bytes"
+ "context"
+ "os"
+ "testing"
+
+ "github.com/arr-ai/arrai/pkg/ctxfs"
+ "github.com/arr-ai/arrai/pkg/ctxrootcache"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRunTests_Pass(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1)"}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "/test")
+
+ require.NoError(t, err)
+ shouldContain(t, buf.String(), "PASS", "test1")
+}
+
+func TestRunTests_EmptyPath(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1)"}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "")
+
+ require.NoError(t, err)
+ shouldContain(t, buf.String(), "PASS", "test1")
+}
+
+func TestRunTests_Fail(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1, test2: 2 = 3)"}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "/test")
+
+ require.Error(t, err)
+ shouldContain(t, buf.String(), "PASS", "test1", "FAIL", "test2")
+}
+
+func TestRunTests_Invalid(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "(test1: 1 = 1, test2: 2)"}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "/test")
+
+ require.Error(t, err)
+ shouldContain(t, buf.String(), "PASS", "test1", "??", "test2")
+}
+
+func TestRunTests_BadArrai(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "x"}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "/test")
+
+ require.Error(t, err)
+}
+
+func TestRunTests_InvalidDir(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{}
+ buf := &bytes.Buffer{}
+ err := RunTests(withFs(t, fsContent), buf, "/nowhere")
+
+ require.Error(t, err)
+}
+
+func TestGetTestFiles_NoFiles(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/placeholder.txt": ""}
+ _, err := getTestFiles(withFs(t, fsContent), "/test")
+ require.Error(t, err)
+}
+
+func TestGetTestFiles_InvalidDir(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/placeholder.txt": ""}
+ _, err := getTestFiles(withFs(t, fsContent), "/nowhere")
+ require.Error(t, err)
+}
+
+func TestGetTestFiles_OneFile(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{"/test/1_test.arrai": "source1"}
+ files, err := getTestFiles(withFs(t, fsContent), "/test")
+ require.NoError(t, err)
+ require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files)
+}
+
+func TestGetTestFiles_PathIsFile(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{
+ "/test/1_test.arrai": "source1",
+ "/test/2_test.arrai": "source2",
+ }
+ files, err := getTestFiles(withFs(t, fsContent), "/test/1_test.arrai")
+ require.NoError(t, err)
+ require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files)
+}
+
+func TestGetTestFiles_NestedDir(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{
+ "/test/1_test.arrai": "source1",
+ "/test/must/go/deeper/2_test.arrai": "source2",
+ }
+ files, err := getTestFiles(withFs(t, fsContent), "/test")
+ require.NoError(t, err)
+ require.Equal(t, []testFile{
+ {path: "/test/1_test.arrai", source: "source1"},
+ {path: "/test/must/go/deeper/2_test.arrai", source: "source2"},
+ }, files)
+}
+
+func TestGetTestFiles_SkipHiddenDir(t *testing.T) {
+ t.Parallel()
+
+ fsContent := map[string]string{
+ "/test/1_test.arrai": "source1",
+ "/test/.must/go/deeper/2_test.arrai": "source2",
+ "/test/must/.go/deeper/3_test.arrai": "source3",
+ "/test/must/go/.deeper/4_test.arrai": "source4",
+ }
+ files, err := getTestFiles(withFs(t, fsContent), "/test")
+ require.NoError(t, err)
+ require.Equal(t, []testFile{{path: "/test/1_test.arrai", source: "source1"}}, files)
+}
+
+func withFs(t *testing.T, files map[string]string) context.Context {
+ err := os.Chdir("/")
+ require.NoError(t, err)
+ fs := ctxfs.CreateTestMemMapFs(t, files)
+ ctx := ctxfs.SourceFsOnto(context.Background(), fs)
+ return ctxrootcache.WithRootCache(ctx)
+}
+
+func TestRunFile_InvalidArrai(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{source: "invalid arr.ai code"}
+ err := runFile(context.Background(), &file)
+ require.Error(t, err)
+}
+
+func TestRunFile_AssertFails(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{source: "//test.assert.equal(1, 2)"}
+ err := runFile(context.Background(), &file)
+ require.Error(t, err)
+}
+
+func TestRunFile_TwoPass(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{source: "(test1: 1 = 1, test2: //test.assert.equal(2, 2))"}
+ err := runFile(context.Background(), &file)
+ require.NoError(t, err)
+ require.NotZero(t, file.wallTime)
+ require.ElementsMatch(t, file.results, []testResult{
+ {name: "test1", outcome: Passed},
+ {name: "test2", outcome: Passed}})
+}
+
+func TestRunFile_OneFailOnePass(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{source: "(test1: 1 < 1, test2: 5 < 7)"}
+ err := runFile(context.Background(), &file)
+ require.NoError(t, err)
+ require.NotZero(t, file.wallTime)
+ require.ElementsMatch(t, file.results, []testResult{
+ {name: "test1", outcome: Failed, message: "Expected: true. Actual: false."},
+ {name: "test2", outcome: Passed}})
+}
+
+func TestRunFile_OneInvalidOnePass(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{source: "(test1: 1, test2: 5 < 7)"}
+ err := runFile(context.Background(), &file)
+ require.NoError(t, err)
+ require.NotZero(t, file.wallTime)
+ require.ElementsMatch(t, file.results, []testResult{
+ {name: "test1", outcome: Invalid,
+ message: "Could not determine test outcome due to non-boolean result of type number: 1"},
+ {name: "test2", outcome: Passed}})
+}
+
+func TestRunFile_TestInSet(t *testing.T) {
+ t.Parallel()
+
+ file := testFile{
+ path: "some_test.arrai",
+ source: "(test1: 1 = 1, category1: { 5 < 7 })",
+ }
+ err := runFile(context.Background(), &file)
+ require.NoError(t, err)
+ require.NotZero(t, file.wallTime)
+ require.ElementsMatch(t, file.results, []testResult{
+ {name: "test1", outcome: Passed},
+ {name: "category1", outcome: Invalid, message: "Sets are not allowed as test containers. " +
+ "Please use tuples, dictionaries or arrays."}})
+}
diff --git a/internal/test/stats.go b/internal/test/stats.go
new file mode 100644
index 00000000..034ed07f
--- /dev/null
+++ b/internal/test/stats.go
@@ -0,0 +1,57 @@
+package test
+
+import (
+ "time"
+ "unicode/utf8"
+)
+
+// calcStats calculates aggregate statistics for all test results in the provided test files. Most importantly, it
+// determines if the test run has succeeded or failed. A test run fails if there are any testResults with a 'failed' or
+// 'invalid' outcome.
+func calcStats(testFiles []testFile) testStats {
+ var stats testStats
+
+ for _, testFile := range testFiles {
+ stats.wallTime += testFile.wallTime
+
+ for _, result := range testFile.results {
+ if count := utf8.RuneCountInString(result.name); count > stats.maxNameLen {
+ stats.maxNameLen = count
+ }
+
+ if count := utf8.RuneCountInString(testFile.path); count > stats.maxFileLen {
+ stats.maxFileLen = count
+ }
+
+ stats.total++
+
+ switch result.outcome {
+ case Invalid:
+ stats.invalid++
+ case Passed:
+ stats.passed++
+ case Ignored:
+ stats.ignored++
+ case Failed:
+ stats.failed++
+ }
+ }
+ }
+
+ stats.runFailed = stats.failed > 0 || stats.invalid > 0
+
+ return stats
+}
+
+type testStats struct {
+ runFailed bool
+ wallTime time.Duration
+ maxNameLen int
+ maxFileLen int
+
+ total int
+ invalid int
+ passed int
+ ignored int
+ failed int
+}
diff --git a/internal/test/stats_test.go b/internal/test/stats_test.go
new file mode 100644
index 00000000..6b840a3f
--- /dev/null
+++ b/internal/test/stats_test.go
@@ -0,0 +1,66 @@
+//nolint:lll
+package test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+var passFile = testFile{
+ path: "file1",
+ source: "src1",
+ wallTime: 100,
+ results: []testResult{
+ {name: "Test1", outcome: Passed},
+ {name: "Test22", outcome: Passed},
+ {name: "Test333", outcome: Passed},
+ {name: "Test4444", outcome: Passed},
+ },
+}
+var failFile = testFile{
+ path: "file22",
+ source: "src2",
+ wallTime: 50,
+ results: []testResult{
+ {name: "Test1", outcome: Passed},
+ {name: "Test2", outcome: Failed},
+ {name: "Test3", outcome: Ignored},
+ },
+}
+var invalidFile = testFile{
+ path: "file3",
+ source: "src3",
+ wallTime: 3,
+ results: []testResult{
+ {name: "Test1", outcome: Invalid},
+ },
+}
+
+func TestCalcStats_AllPass(t *testing.T) {
+ t.Parallel()
+ require.Equal(t,
+ testStats{runFailed: false, wallTime: 100, maxNameLen: 8, maxFileLen: 5, total: 4, invalid: 0, passed: 4, ignored: 0, failed: 0},
+ calcStats([]testFile{passFile}))
+}
+
+func TestCalcStats_Mixed(t *testing.T) {
+ t.Parallel()
+ require.Equal(t,
+ testStats{runFailed: true, wallTime: 153, maxNameLen: 8, maxFileLen: 6, total: 8, invalid: 1, passed: 5, ignored: 1, failed: 1},
+ calcStats([]testFile{passFile, failFile, invalidFile}))
+}
+
+func TestCalcStats_Failed(t *testing.T) {
+ t.Parallel()
+ require.Equal(t,
+ testStats{runFailed: true, wallTime: 50, maxNameLen: 5, maxFileLen: 6, total: 3, invalid: 0, passed: 1, ignored: 1, failed: 1},
+ calcStats([]testFile{failFile}))
+}
+
+func TestCalcStats_Invalid(t *testing.T) {
+ t.Parallel()
+ require.Equal(t,
+ testStats{runFailed: true, wallTime: 3, maxNameLen: 5, maxFileLen: 5, total: 1, invalid: 1, passed: 0, ignored: 0, failed: 0},
+ calcStats([]testFile{invalidFile}))
+}
diff --git a/internal/test/test.go b/internal/test/test.go
deleted file mode 100644
index 7306233d..00000000
--- a/internal/test/test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package test
-
-import (
- "context"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/arr-ai/arrai/pkg/ctxfs"
- "github.com/arr-ai/arrai/rel"
- "github.com/arr-ai/arrai/syntax"
-)
-
-// Test runs all tests in the subtree of path and returns the results.
-func Test(ctx context.Context, w io.Writer, path string) (Results, error) {
- results := Results{}
- var files []string
- err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
- if strings.HasSuffix(path, "_test.arrai") {
- files = append(files, path)
- }
- return nil
- })
- if err != nil {
- return Results{}, err
- }
-
- if len(files) == 0 {
- return results, fmt.Errorf("no tests found in %v", path)
- }
-
- fmt.Fprintf(w, "Tests:\n%s\n", strings.Join(files, "\n"))
-
- for _, file := range files {
- bytes, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), file)
- if err != nil {
- return results, err
- }
- result, err := syntax.EvaluateExpr(ctx, file, string(bytes))
- if err != nil {
- fmt.Fprintf(w, "\nfailed test: %s\n", err)
- results.Add(Result{file: file, pass: false})
- } else {
- results.Add(Result{file: file, pass: isRecursivelyTrue(result)})
- }
- }
-
- return results, nil
-}
-
-// isRecursivelyTrue returns true if every leaf value of val is true (not just truthy).
-func isRecursivelyTrue(val rel.Value) bool {
- switch v := val.(type) {
- case rel.EmptySet:
- return false
- case rel.GenericSet:
- if v.Count() == 1 && v.Has(rel.NewTuple()) {
- return true
- }
- for _, item := range v.OrderedValues() {
- if !isRecursivelyTrue(item) {
- return false
- }
- }
- return true
- case rel.Array:
- if v.Count() == 0 {
- return false
- }
- for _, item := range v.Values() {
- if !isRecursivelyTrue(item) {
- return false
- }
- }
- return true
- case rel.Dict:
- if v.Count() == 0 {
- return false
- }
- for _, entry := range v.OrderedEntries() {
- if !isRecursivelyTrue(entry.MustGet(rel.DictValueAttr)) {
- return false
- }
- }
- return true
- case rel.Tuple:
- for e := v.Enumerator(); e.MoveNext(); {
- _, attr := e.Current()
- if !isRecursivelyTrue(attr) {
- return false
- }
- }
- return true
- }
- return false
-}
diff --git a/internal/test/value_utils.go b/internal/test/value_utils.go
new file mode 100644
index 00000000..7c8360ea
--- /dev/null
+++ b/internal/test/value_utils.go
@@ -0,0 +1,56 @@
+package test
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/arr-ai/arrai/rel"
+)
+
+// ForeachLeaf visits all leaves in an test tree, invoking the leafAction callback for each leaf encountered.
+// Tuples, arrays and dictionaries are considered test containers and not leaves, they are recursed into.
+func ForeachLeaf(val rel.Value, path string, leafAction func(val rel.Value, path string)) {
+ path = strings.TrimPrefix(path, ".")
+
+ switch v := val.(type) {
+ case rel.Array:
+ for i, item := range v.Values() {
+ ForeachLeaf(item, fmt.Sprintf("%s(%d)", path, i), leafAction)
+ }
+ case rel.Dict:
+ for _, entry := range v.OrderedEntries() {
+ key := entry.MustGet("@")
+ keyStr := key.String()
+ if _, ok := key.(rel.String); ok {
+ keyStr = "'" + keyStr + "'"
+ }
+ ForeachLeaf(entry.MustGet(rel.DictValueAttr), fmt.Sprintf("%s(%s)", path, keyStr), leafAction)
+ }
+ case rel.Tuple:
+ for e := v.Enumerator(); e.MoveNext(); {
+ name, attr := e.Current()
+ ForeachLeaf(attr, fmt.Sprintf("%s.%s", path, name), leafAction)
+ }
+ default:
+ if path == "" {
+ leafAction(val, "")
+ } else {
+ leafAction(val, path)
+ }
+ }
+}
+
+// isLiteralTrue returns true if and only if the provided value is a literal true, i.e. "true" or "{()}"
+func isLiteralTrue(val rel.Value) bool {
+ v, ok := val.(rel.GenericSet)
+ return ok && v.Count() == 1 && v.Has(rel.EmptyTuple)
+}
+
+// isLiteralFalse returns true if and only if the provided value is a literal false, i.e. "false" or "{}"
+func isLiteralFalse(val rel.Value) bool {
+ if _, ok := val.(rel.EmptySet); ok {
+ return true
+ }
+ v, ok := val.(rel.GenericSet)
+ return ok && v.Count() == 0
+}
diff --git a/internal/test/value_utils_test.go b/internal/test/value_utils_test.go
new file mode 100644
index 00000000..9a0b5dd0
--- /dev/null
+++ b/internal/test/value_utils_test.go
@@ -0,0 +1,134 @@
+package test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/arr-ai/arrai/pkg/arraictx"
+ "github.com/arr-ai/arrai/rel"
+ "github.com/arr-ai/arrai/syntax"
+ "github.com/stretchr/testify/require"
+)
+
+func TestIsLiteralTrue(t *testing.T) { //nolint:dupl
+ t.Parallel()
+
+ require.True(t, isLiteralTrue(eval("true")))
+ require.True(t, isLiteralTrue(eval("{()}")))
+
+ require.False(t, isLiteralTrue(eval("false")))
+ require.False(t, isLiteralTrue(eval("1")))
+ require.False(t, isLiteralTrue(eval("0")))
+ require.False(t, isLiteralTrue(eval("()")))
+ require.False(t, isLiteralTrue(eval("{true}")))
+ require.False(t, isLiteralTrue(eval("{false}")))
+ require.False(t, isLiteralTrue(eval("{(1)}")))
+ require.False(t, isLiteralTrue(eval("{(),1}")))
+ require.False(t, isLiteralTrue(eval("[true]")))
+ require.False(t, isLiteralTrue(eval("(val: true)")))
+}
+
+func TestIsLiteralFalse(t *testing.T) { //nolint:dupl
+ t.Parallel()
+
+ require.True(t, isLiteralFalse(eval("false")))
+ require.True(t, isLiteralFalse(eval("{}")))
+
+ require.False(t, isLiteralFalse(eval("true")))
+ require.False(t, isLiteralFalse(eval("1")))
+ require.False(t, isLiteralFalse(eval("0")))
+ require.False(t, isLiteralFalse(eval("()")))
+ require.False(t, isLiteralFalse(eval("{true}")))
+ require.False(t, isLiteralFalse(eval("{false}")))
+ require.False(t, isLiteralFalse(eval("{(1)}")))
+ require.False(t, isLiteralFalse(eval("{(),1}")))
+ require.False(t, isLiteralFalse(eval("[true]")))
+ require.False(t, isLiteralFalse(eval("(val: true)")))
+}
+
+func TestForeachLeaf(t *testing.T) {
+ t.Parallel()
+
+ // No root
+ require.Equal(t,
+ leavesShouldBe{"": "true"},
+ forInput("true"))
+ require.Equal(t,
+ leavesShouldBe{"": "false"},
+ forInput("false"))
+ require.Equal(t,
+ leavesShouldBe{"": "42"},
+ forInput("42"))
+
+ // Tuple root
+ require.Equal(t,
+ leavesShouldBe{},
+ forInput("()"))
+ require.Equal(t,
+ leavesShouldBe{"a": "true", "b": "false"},
+ forInput("(a: true, b: false)"))
+ require.Equal(t,
+ leavesShouldBe{"a.b.c": "true", "d.e.f": "false"},
+ forInput("(a: (b: (c: true)), d: (e: (f: false)))"))
+ require.Equal(t,
+ leavesShouldBe{"a(0)": "0", "a(1)": "1", "a(2)": "'2'"},
+ forInput("(a: [0, 1, '2'])"))
+ require.Equal(t,
+ leavesShouldBe{"a(0)(0)": "0", "a(0)(1)": "1", "a(1)(0)(0)": "100", "a(1)(1)": "11"},
+ forInput("(a: [[0, 1], [[100], 11]])"))
+ require.Equal(t,
+ leavesShouldBe{"a('k0')": "0", "a('k1')": "1", "a(2)": "2", "a((x: 3))": "3"},
+ forInput("(a: {'k0': 0, 'k1': 1, 2: 2, (x: 3):3})"))
+
+ // Array root
+ require.Equal(t,
+ leavesShouldBe{"": "false"}, // An unfortunate side effect of everything being a set
+ forInput("[]"))
+ require.Equal(t,
+ leavesShouldBe{"(0)": "true", "(1)": "false", "(2).a": "1", "(2).b": "'2'", "(2).c('three')": "3", "(2).c(4)": "'4'"},
+ forInput("[true, false, (a: 1, b: '2', c: { 'three': 3, 4: '4' })]"))
+
+ // Dictionary root
+ require.Equal(t,
+ leavesShouldBe{"(0)": "true", "(1)": "false", "(2).a": "1", "(2).b": "'2'", "(2).c('three')": "3", "(2).c(4)": "'4'"},
+ forInput("{0: true, 1: false, 2: (a: 1, b: '2', c: { 'three': 3, 4: '4' })}"))
+}
+
+type leavesShouldBe map[string]string
+
+// Runs ForeachLeaf on the result of the arrai source. Returns a dictionary of results (node path -> leaf value) or
+// or nil if it failed to parse (instead of err, so it can be inlined)
+func forInput(source string) leavesShouldBe {
+ leaves := make(leavesShouldBe)
+ tree, err := evalErr(source)
+ if err != nil {
+ return leavesShouldBe{"PARSING ERROR": err.Error()}
+ }
+
+ ForeachLeaf(tree, "", func(val rel.Value, path string) {
+ valStr := val.String()
+ if valStr == "{}" {
+ valStr = "false"
+ } else if _, ok := val.(rel.String); ok {
+ valStr = "'" + valStr + "'"
+ }
+ leaves[path] = valStr
+ })
+
+ return leaves
+}
+
+func eval(source string) rel.Value {
+ value, _ := evalErr(source) //nolint:errcheck
+ return value
+}
+
+// Evaluates the arrai source and returns the result, or nil if it failed to parse (instead of err so it can be inlined)
+func evalErr(source string) (rel.Value, error) {
+ ctx := arraictx.InitRunCtx(context.Background())
+ value, err := syntax.EvaluateExpr(ctx, "", source)
+ if err != nil {
+ return nil, err
+ }
+ return value, nil
+}
diff --git a/pkg/arrai/out_test.go b/pkg/arrai/out_test.go
index 02fbe2f8..84abbde6 100644
--- a/pkg/arrai/out_test.go
+++ b/pkg/arrai/out_test.go
@@ -463,7 +463,7 @@ func testAssertFiles(t *testing.T, dir, script string, files map[string]string,
content, exists := files[relPath]
assert.True(t, exists, fmt.Sprintf("unexpected file exists: path=%s, relPath=%s", path, relPath))
- f, err := ctxfs.ReadFile(fs, path)
+ f, err := afero.ReadFile(fs, path)
assert.NoError(t, err)
assert.Equal(t, content, string(f))
diff --git a/pkg/ctxfs/ctxfs.go b/pkg/ctxfs/ctxfs.go
index 28f09690..c3c3e9ee 100644
--- a/pkg/ctxfs/ctxfs.go
+++ b/pkg/ctxfs/ctxfs.go
@@ -2,7 +2,6 @@ package ctxfs
import (
"context"
- "io/ioutil"
"path/filepath"
"runtime"
"strings"
@@ -52,16 +51,6 @@ func RuntimeFsFrom(ctx context.Context) afero.Fs {
return defaultFs
}
-func ReadFile(fs afero.Fs, filePath string) ([]byte, error) {
- f, err := fs.Open(filePath)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- return ioutil.ReadAll(f)
-}
-
func ToUnixPath(p string) string {
//nolint:goconst
if runtime.GOOS == "windows" {
diff --git a/syntax/bindata.go b/syntax/bindata.go
index c66b6930..61b5e45d 100644
--- a/syntax/bindata.go
+++ b/syntax/bindata.go
@@ -1,7 +1,7 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// syntax/implicit_import.arrai (921B)
-// syntax/stdlib.arraiz (3.361kB)
+// syntax/stdlib.arraiz (3.37kB)
package syntax
@@ -90,7 +90,7 @@ func syntaxImplicit_importArrai() (*asset, error) {
return a, nil
}
-var _syntaxStdlibArraiz = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x96\x67\x58\x53\x69\x9b\xc7\x0f\x2d\x74\x11\xa5\x58\x40\x29\x52\x0d\x04\x41\x41\x9a\x80\xd2\x4c\x28\x86\xde\x25\x42\x42\x82\x89\x41\x8a\x80\x14\x31\x10\x40\xa4\xc8\x4b\x0d\x4a\x11\x69\x41\x14\x11\x44\x09\x91\x08\x41\x05\x82\x09\xd2\x41\x11\xc4\x42\x0b\x02\x43\x93\xbe\xd7\x3b\xb3\xb3\xab\xee\xb5\xb3\xbb\xb3\xef\xf9\x72\xae\xf3\xe1\xb9\xdb\xf9\xdf\xff\xe7\x77\x0e\xc6\xc5\x2d\x06\xf0\x01\x7c\xc0\xaf\x8f\x10\x00\x00\x3e\xf8\x4b\x28\x8c\x9f\x3a\x22\x30\x10\x81\xe9\x6e\xb3\xee\xec\xbc\x09\xe9\x60\x41\x6a\xec\xce\x41\x59\xe0\x76\x65\xeb\x6e\x18\x93\xd5\x67\x0b\x56\xed\xb6\xee\xe4\x86\xdf\x20\x41\xf5\x04\xbc\x1d\x31\x24\x5d\xb2\x93\x00\x74\xbd\x61\x4d\xa5\x54\x97\xbc\x7e\xd2\x61\x46\xd2\xf9\x6b\xd6\x11\xa7\x49\xc9\x9c\x92\xcf\xbe\x8a\x26\x2a\x86\x89\x26\x79\x31\x00\x00\xec\xec\x9c\x83\xf1\xf2\xa9\x38\x59\x4e\xda\x01\x00\x80\x07\x00\xe0\xbf\xaf\x45\x01\x00\x00\x1c\xde\x37\x04\x8b\x84\xf8\x61\x82\xd1\x21\x17\xd4\x7d\xf0\x38\x08\x22\x30\x50\x0d\x81\x81\xfc\x5e\x1d\xc4\x0f\xaf\x8e\xc3\xfb\xc6\x65\x32\xae\xd6\x69\x0b\xc5\x0f\xe4\xdf\xf7\x1e\x52\x9f\x27\xd1\xc1\xc3\x9e\x85\x5e\x2f\xae\xf1\xb4\xf0\x05\xc4\xd7\x90\x35\x32\x34\x9c\x47\x13\xee\x5a\xbf\x2a\x69\xa7\xfd\x76\xba\xd0\x2b\x2a\xdb\xf7\xfc\x99\xc6\xe4\xdc\x65\xf5\x5d\x30\x3a\xf6\x59\xa0\x4c\x47\x33\x8e\xf7\x59\x0f\x59\x84\xe4\x6b\x57\x68\x13\xd7\xc3\xa7\x0d\x67\xda\x2b\x9d\x48\xd8\xa5\xe2\xab\xd3\x7e\xfc\xe8\xe3\xd1\x1d\xcb\x73\xdb\x05\x3a\x63\x90\xc3\x07\x5e\x76\xc6\xaf\x47\x5c\x8e\x1b\xaa\x2e\x3f\x5f\xd0\x4a\x11\xbb\xb6\xfe\x9d\x6b\xf6\xbc\x58\xb6\xca\xce\x65\x57\x0b\x99\x54\xbc\x84\x45\xa8\x45\xb4\xfd\x90\xf5\xb1\x37\xa6\xff\xe0\x96\x1f\x3e\x19\x77\xb4\xf3\x4e\x40\x31\x5b\x31\xd7\x5b\xce\xf9\x34\xff\x5d\x47\xd9\x64\x78\x65\x47\x4a\x55\x8a\x43\xf1\xd1\xa3\x3e\x5a\x4c\xad\x09\x77\x0b\xb4\x4f\xd2\x3a\x8b\x1d\x3c\x15\x9a\x6a\x47\xf6\xc6\x5b\x59\x7c\x4c\x8d\xb6\x6f\x1b\x9f\x44\x39\x0b\x69\x88\xb8\x6f\xde\x9c\x37\x95\x8d\x17\x29\x7f\x2c\x61\xae\x02\x27\x55\x2a\x07\xf6\x85\x08\xd7\x0f\xca\x48\xec\xdc\xde\x18\xfe\x7e\x09\xd6\x37\xfe\xa2\x9e\x1e\x65\x36\x1e\xd7\x95\xca\xf4\x21\x40\x88\xd9\x6e\x59\x07\xb7\x3d\xc8\xa0\x2c\x8c\x75\xca\xd9\xab\x87\x03\x8f\x08\x1c\x5f\xdc\x1a\x44\x29\x90\x0c\xd0\x6d\x29\xf2\xf3\xce\xd8\xaf\xde\x90\x7d\x3a\xe9\x08\x4c\x86\x54\xd8\x41\x73\x2a\x61\x22\xe8\x88\xe0\x22\x3d\xc9\x57\x0f\xee\x29\x6b\x4e\x13\x0f\xd1\x6a\x7d\xf6\xed\x12\x12\x9a\xa7\xbe\xd5\xb8\xbf\x99\xc9\xab\xb5\x44\x58\xd9\x75\xaf\x31\xe0\x70\xf1\x4c\xbf\x67\xc6\x1e\x54\x32\x5d\xb8\x42\xc4\x5d\xa1\xc5\x72\xe6\x9b\xc2\xec\x7b\x36\x9a\x84\x5a\x8a\x14\x32\x54\x4b\xbb\x9d\x63\xd1\x5e\x6e\x66\x4b\x4f\xd7\x18\xbe\x1e\x86\xf6\xfa\x70\xa8\x07\x31\xce\x38\x11\x21\x69\x71\x27\xf4\x93\xfb\xf3\xa5\x40\xc1\x90\x80\x33\x97\x2c\x56\xe7\x86\xef\x27\xdc\xd5\x20\xcc\xb9\x16\x15\xd0\x2e\xc3\x66\x96\xa8\x66\xd0\x44\x37\x7e\x05\xa4\x42\x12\xf5\x51\x16\x81\xd2\x3f\x01\x6d\x69\x6e\xd3\xa2\xe3\xe5\x23\x39\xef\x10\xd3\xfd\xec\x6f\xeb\x41\xc4\x83\xa5\xd1\xb8\x3c\xe8\x35\x2e\xbe\x47\xad\x26\xd2\xfb\xa0\x67\x71\x0e\xf9\x92\x0f\x54\xdd\x6e\x9d\x7b\xfa\x55\xff\x15\x95\xc9\x6b\x2c\x1b\xa7\x22\x98\xa1\xa3\xec\x8e\xab\xe3\xac\x68\x94\x97\x59\x92\x7f\xac\x64\x93\xb6\x3d\x5f\xb5\xb2\xc1\x87\xa8\x4b\x6f\x40\xbb\x71\x4c\x14\x83\x6e\xcd\xda\xc2\x8f\x0f\xbd\xce\x5c\xdd\xda\x11\xbb\xd6\x8b\xc9\xfe\xb6\x90\x3f\x6b\x6b\x60\x95\x53\xa0\xcb\x72\x7e\x96\xca\x5e\xbb\x07\xe5\xd2\x6b\x9c\x12\x09\x2d\x05\x97\xf4\x9a\x4f\x4b\x89\xaa\xe5\x52\xe8\x95\xaa\x7b\x43\x69\xf9\xdb\xfc\x7f\x4a\x56\x0d\xc1\x2b\xe0\xcd\x09\x00\x85\x3c\x7f\x25\x59\x9d\xff\x59\xb2\x41\xe1\x97\x82\x11\x61\x90\xa0\x60\x5f\x2c\xe6\x02\x04\x85\x45\xfc\xfb\xa6\x55\xb9\xbc\x09\x2a\xb6\x14\xa3\xb1\xce\x2f\x94\xc5\x0b\xbc\x49\xca\x8f\xf0\xeb\x75\xc0\x5d\x49\xcd\x60\xf4\x7b\x5d\x70\x72\xc3\x64\xa9\x72\xdf\xe7\xd0\x52\x89\xdf\x89\x9a\x59\xe6\x2d\x8a\xc9\x7d\x40\x8d\x95\xa3\x46\x9f\xa7\xad\x12\x19\x60\x91\x4f\xe8\x2d\x1e\x40\x46\x42\x77\xb7\x22\x51\xc4\xca\x92\x04\xdd\x3c\xb5\x30\xc8\x0b\x25\xf3\x6a\x75\x55\x89\x9c\xc0\xb7\xd5\x3e\x6f\xf3\x06\xd9\x75\x59\x5f\x3d\x69\x5e\x23\xd3\x4c\xeb\x99\x92\x9b\x3f\x50\xe6\x61\x9e\xa0\xc4\xa8\xb0\xed\xaa\x72\xd9\x3b\x70\xc2\x6a\xd4\x38\x29\xff\x3b\x8f\x3b\xe9\x50\x0c\xac\x5d\x7e\xbd\xde\xa6\xfe\x49\x8c\xf2\x86\xab\x54\xff\x69\xaf\x69\x57\x1b\x49\xdf\xe6\xec\x8f\xdc\x46\x53\x9a\x9b\x74\xf7\xcd\x57\x73\x37\x2c\x24\xd8\x5f\x20\x0b\xeb\x9a\xf9\x54\xf5\xf9\xd5\x16\x7e\x83\xab\x63\x6b\xa8\x51\xcd\x99\xad\x0a\xb0\xea\x08\x38\x07\xfe\x38\x28\x2a\x60\x00\x73\xe0\x53\x68\xf1\x52\x43\xd6\x57\xc5\xe9\xbd\x18\xc8\xb8\x7c\xec\x17\x45\xeb\xfa\xb7\x0f\x9b\x2e\xf2\x97\x4e\x5b\x68\xd4\x4a\x92\x85\x88\x1e\x34\xae\xa8\xb8\x35\xa9\xec\xcf\xe9\xcd\xda\x5f\x1b\x0b\x37\x65\x22\xc3\x0c\x16\x9e\xed\x5e\x67\x16\x26\x8d\xce\xf0\x99\x0d\x97\x9c\x1d\xbc\x40\x78\x9c\x23\x78\x43\x23\x98\x91\xc0\x73\x54\xb3\x69\x6f\x67\xc8\x42\xde\x6b\x51\x56\x67\x88\x72\x3d\x35\x31\x68\x59\x10\x8e\x34\x76\xf1\x49\x3f\xf5\x35\xf6\xd4\x78\xac\xa8\x9f\x6d\xf8\x76\x11\xca\x97\x7a\xab\x94\xe5\xff\x25\x66\xbd\xce\x7a\xba\x3b\x8f\xe3\xa1\xbe\x4c\xdd\xf4\xfb\xa8\xc9\xf7\x97\x09\x1f\xee\xb1\x5e\x1b\x40\x33\x92\x85\x0f\xcc\x64\x82\x0a\xd6\x22\x2d\xe3\x4b\xa7\x7a\x55\xc8\x21\x95\x81\xd0\xca\xcd\xeb\x8d\xfa\x01\x9c\x87\xcb\x56\xcc\xc5\xc7\xb4\x96\xf6\x35\x31\xad\xe4\x64\x33\x04\xd5\xad\x14\x8b\x0a\x1c\x98\x1a\x01\xd9\x15\xfc\x56\x33\x55\x7b\x6f\xe9\xb9\xd9\x60\xf6\xab\xce\x51\x46\x46\xe6\x97\xd3\xed\xed\x78\x3a\x42\xbe\xc5\xd4\x20\x7b\x91\xfc\x07\xc6\x52\x3d\x50\xa7\x57\x72\x40\xd9\x2f\xa1\xce\xa6\x68\x46\x31\x58\xa5\x34\xcb\x86\x28\xce\xd5\x87\x4d\xdc\x9b\x5b\x9e\x20\xc7\x40\xa8\xc5\x9c\xd9\x87\xdb\x0d\x55\x1f\x8d\xb2\xec\xa6\x82\x1e\x0a\x50\xa7\xef\xde\x43\xde\x7e\x21\x94\xad\x74\x88\x1e\x08\x09\x1b\x74\x2f\x7a\xa2\xd0\xc6\xa1\x20\xf1\xb9\x92\xdc\x9a\xb5\x4a\xd2\x76\xcd\xd7\x43\x32\x97\x56\x5e\x1d\x4c\x39\xcb\xd4\xe3\xaa\x21\x26\x96\x4e\x0b\x2b\xd5\xf9\xbf\x7a\x79\x2c\x7e\xff\x9d\xb0\x2a\xfb\x7f\x54\xd8\xe2\x9b\xeb\x0d\x98\xa6\x03\x9a\xb0\x8e\xca\x19\xd2\x8d\xe2\xdb\x89\xe8\x50\x8f\xf2\x5e\xd2\x9c\xff\x8e\x29\x28\x41\x8e\x79\xcf\x07\xec\x50\xdd\x92\x17\x0a\x35\xb8\x21\x75\x36\x91\xd6\xd5\xd0\x21\xfc\xb0\x4e\xb4\x44\x3c\xb6\x3b\x58\x38\xc4\x3b\xb7\xd1\x08\xb7\xb1\x06\xf7\x4f\xab\xe0\x68\xcb\xbb\xb4\x71\xed\xba\x56\x88\xdb\xaa\x49\x8a\x4c\xc5\xbe\x47\x14\xb8\x0e\x36\xa2\xc0\x70\x48\x93\x85\x94\xa5\x72\xd0\x36\x82\xf3\x31\xe8\xc9\xd3\xce\xee\xb1\x1d\x37\xde\x39\x16\x01\x69\x29\xa5\x02\xf0\xad\xb9\x46\x14\x8b\xc2\xfc\x80\x05\xd0\x54\x6c\x8b\x4a\x4d\x3d\xd0\x63\x6a\x4c\xe1\x29\xef\xe5\xa2\x1e\x7a\x94\xf5\xc0\xbc\xcf\x3f\x21\x10\x6b\x8c\x80\xed\x97\xc3\xbf\x73\xc8\xbc\x13\x26\x45\x1c\x0c\x15\xdd\xf2\x22\xf7\xd1\x73\x98\x86\x17\xba\x9f\x3e\x34\xcb\xed\x6c\x76\x08\x3c\x5a\x7b\xfc\x8c\x7e\x9f\xe2\xe2\xca\xe2\x22\xae\xe8\xdd\xdc\x34\xf2\xd3\x76\x53\xc9\xaa\xd9\xa1\x97\xe2\x59\x72\x60\x6d\x42\xb0\xd5\xb0\x62\x34\xce\xc9\xd5\x33\x17\xee\x54\x30\x3a\xe4\xc2\xf4\x5c\x56\x7f\xbf\x09\xba\x66\x64\x64\xa8\xf5\x64\x4f\xb3\x89\xef\x49\x33\x3d\x50\x65\x4d\x8c\x21\x89\xe1\xa9\x6d\xe7\xd7\x86\x9a\xc0\x42\xcc\xe5\xdb\xfd\xb4\x61\x4f\xbe\x88\x1e\x9d\x84\x32\xe2\xfa\xaf\x5e\xdd\x2d\xdb\xe9\x76\xd3\x3f\x28\xfa\x19\x6e\x78\x31\x77\x7c\xb9\xc9\x4f\xaf\x3a\x7d\x4a\x2d\xb7\x81\xa2\x5e\xe6\xe4\xc7\xb0\x81\xd8\x9b\xfb\x0a\xaa\x10\xd2\x9e\x13\x87\x15\x73\x67\x56\xf0\x37\x35\x06\x39\x4d\xfc\xae\xc2\xa4\x99\x7a\x0f\xd8\x67\x22\x5c\x20\x99\x76\xd8\xe3\x63\x46\x8f\x08\x42\x93\xfe\x52\x22\x74\xb2\x4b\xfc\xb1\x28\x13\x98\xa3\x0f\x23\x5c\x8c\x96\x1a\x3b\x34\x64\xb4\xf0\xa0\x94\xbf\xe8\xb7\x4d\x90\xd5\x68\x71\x64\x9a\xd7\x66\xec\x08\x8b\x2d\x46\x7c\xe1\xff\x52\x40\x01\x45\x1d\x1a\x77\x44\xba\x6b\x9b\x38\x76\xf6\x25\x1e\xf8\xd2\xe4\x8f\xfd\x04\xbb\xeb\xa5\xe4\xba\xd7\x93\xf0\x71\x9e\xf5\xe6\x88\x3f\x3a\x39\xd9\x49\xe1\x0d\x75\xce\x23\xad\x4c\x01\xa7\x94\x36\x7e\x90\x5d\xb7\x54\x2c\x57\x14\x90\x21\xc7\xb0\x08\x68\x2c\x79\x05\x33\x88\x9c\x2a\xdd\x33\x2b\xec\xb7\xad\x90\xf2\x9a\x6e\x77\x11\x34\xc9\x47\x6e\xbd\xae\x21\x4e\xca\x03\x47\x44\xb7\x2c\xb3\xac\x4c\x07\xc8\xa2\x87\x47\xd1\x60\xcf\xfd\xa1\xd9\xec\x46\xa5\x8d\x80\xcf\xfa\xf0\xc3\x33\xb9\x39\xbe\xa3\xb5\xd6\xc9\xef\xd6\xae\xd4\x0a\x95\x9c\x1a\xe4\x96\x60\x90\x53\xc6\xc2\x94\xc1\x51\x31\x4f\x88\x77\xd5\x87\x2c\x7d\x5e\x9b\x24\x38\x7e\x2c\x9b\x59\xd4\x32\x89\x0d\xea\x73\x1b\x98\x28\x63\x09\x3b\x55\x51\xa4\x5b\xae\x80\xd9\xb5\x28\xe9\xe6\x54\x7d\xd2\x64\xff\xba\x1e\x88\x3e\x1f\xf1\xb1\x8f\x5f\xab\x77\x11\x1f\x5e\x5e\xbd\x0a\x92\x6a\x4f\x31\x42\x3f\xc5\x4b\x87\x87\x2f\xcf\xea\x9b\xbb\x86\x1b\xe6\xd7\x1b\x21\x48\xd9\xf7\x2f\x74\xe1\x69\xa2\xfe\xdf\x83\x83\x76\x0c\x27\xa7\x74\xc6\x10\x7c\x6b\x1f\xa8\xf3\x15\x99\x4f\x77\xb0\xc1\x03\xd2\xb0\xc9\xa2\xef\xe2\xc8\xf0\x4c\x76\x74\x45\x50\x6d\x10\xb1\x56\xb7\xeb\xf5\xc7\x29\x13\x4a\x04\x07\x75\x96\x28\x66\xbc\x4c\xb1\xb9\x6d\x78\xf0\xdb\xb7\x98\x53\x2d\x03\x73\x6c\x8f\x52\xde\xc5\xf0\xad\xf3\x67\x2c\x7a\xbe\x7c\x08\x5f\xc9\x98\x54\xf6\x3f\x34\xc7\xb8\xc6\xf1\xa7\x09\xd3\x63\xf2\x55\x65\x78\x00\xc0\x48\xec\xaf\x4c\x58\xf7\xff\x6a\xc2\x7f\xbc\xfe\xb0\xe1\xc8\xb4\xe6\x4b\xb5\x1a\xbb\xe3\xe6\x0c\x5e\x5a\x36\xc4\xec\xd1\x0d\x8d\xde\x82\x57\x3f\x5f\xcd\x65\x10\x84\x5d\x42\xfa\xdb\xbc\xf2\x4c\xad\x37\xd8\x59\xb2\xa5\x5d\x96\x23\x3d\x63\x24\x7c\xe8\xe0\x58\x69\xad\x68\xcd\xae\x7a\xc9\xa5\x66\x9a\x2b\x4e\x8e\xcf\x8f\x52\xbc\xc0\xf1\xb6\x50\x28\xef\xfa\xc5\x9a\x02\x79\xf0\x19\xbd\x58\xe5\xd6\x3c\x42\x7c\xad\x63\x51\x00\x31\xcc\x01\x34\xe1\x6d\x26\x54\x2e\x46\x2c\x6a\x53\x35\x10\x8c\xf3\xb2\xf6\x40\x0c\xa1\x40\x92\x96\x33\xd2\x97\x05\xa1\x13\xc8\xc9\x7c\x8e\x1a\x73\x0d\x4d\xee\x54\x99\x95\x6e\x52\x40\x28\xd3\x49\xef\x56\xbf\x76\x9f\xdc\x53\xf4\xf7\x41\x59\x7d\xf3\xb7\x4c\xa7\xfd\xc0\x49\xf9\xb6\x42\xf0\x4d\x7e\xab\x87\xad\x4f\x87\x4d\x54\x1c\xc7\x8c\xca\xd8\xb4\x63\x06\x98\xe5\xf0\xdb\xd7\x0f\xd6\xf3\x7e\xbe\x47\x2a\xbb\xdc\x9b\x3d\x38\x37\xda\x77\xaa\x3d\x2d\x52\x2e\x69\xe4\x8e\x86\x47\x04\xae\x61\xe4\x50\x07\x4f\xce\xd7\xc0\x40\x5a\x61\x37\xda\x3e\x02\x77\xfa\x86\xcf\x6c\x43\x8d\xda\x6f\x53\xa5\xad\x35\x94\x24\x92\xe0\x7d\x46\x3b\xb9\xbd\xa3\xfc\xf3\x2e\x95\x73\xe0\xd4\xea\xd0\xed\x27\xb2\x90\x6f\x4b\xf2\x06\xdb\x26\x04\xb5\xd5\xd8\xaa\xa9\x76\x86\x45\xb9\x4b\xba\xd1\xf9\x7b\xaa\x8f\xab\xff\x03\xe1\x42\xae\x00\xf2\x47\x38\x00\xc0\x9f\xf3\x5f\x7a\x1f\x86\x04\x63\xb0\x7f\xfc\x08\x6c\x6a\x73\x52\x9c\xc6\x6e\xd3\xb9\x2d\x71\x9e\x08\xf8\x2d\xb6\xe7\x10\xc4\xd5\xb0\x2e\x9f\x9b\xf3\xad\xb1\x92\x6b\x1c\x72\x3b\x2a\x51\x53\x4b\xc8\xca\x93\x12\xbe\xbc\x6b\x99\xa8\xb6\xa5\x2b\x58\x8d\x89\x2d\x79\x8e\x76\xa1\x5d\xe4\x7f\xfb\x48\x95\x37\x33\x2e\x4f\x3a\xd2\xee\xf3\x05\x1f\x97\x37\x4a\xf7\x51\x39\x87\x0d\xcd\x9c\x8b\x98\x4e\x97\xbf\x37\xb3\xf9\x01\x75\xc5\xae\x78\x17\x6e\xad\xfb\x05\xd6\x37\x4b\x46\x08\x8e\x0b\x3b\xa6\x6a\x34\x06\x9f\xc4\xfe\x0f\x1f\x8f\xbd\xd8\x67\x7f\xb2\x15\x19\x77\xa5\xe9\xa2\x40\x6a\xb4\xb1\xcf\x16\xa3\x29\x7f\xd4\x74\x02\x9e\x97\x48\x59\x18\xee\xeb\x31\xfc\xb3\xfb\x04\x45\xbd\x02\x12\x00\x00\xab\xbf\x03\x2c\x07\xa7\x18\xf0\x9f\xfd\xff\x08\xb7\x42\xff\x65\x22\x3f\xc2\xf5\xaf\x27\x7f\x64\x0c\x85\x9f\x4e\x25\xff\x6f\x51\xf8\xd7\x90\x3f\x6e\x8c\xce\x4f\x21\x8d\xb8\xfe\x36\xaa\xfc\x9a\xe4\x47\x2d\xe8\xfe\x94\xa4\x95\xef\xff\xb1\x8a\xbf\xa6\xf9\x71\xe8\x3f\xf7\x62\x2b\xf0\xb7\x65\x76\x0e\xc6\x03\xfa\x67\x08\x1e\x80\x07\x68\xe2\x00\x00\x6b\xc1\x7f\x7e\xfd\x5b\x00\x00\x00\xff\xff\xb0\x4a\x1a\xfe\x21\x0d\x00\x00")
+var _syntaxStdlibArraiz = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x96\x79\x3c\x94\x7b\xdf\xc7\x2f\xfb\x5e\x84\x48\x84\x91\xb5\x61\x44\x29\x3b\x25\xc9\x52\x91\x99\xec\x65\x99\x31\x33\xcd\x34\xc2\x24\x59\x92\x31\x96\x84\x9c\x6c\x23\x34\x61\x30\x25\x4b\x96\x8e\x31\x99\x2c\x65\x19\x66\xac\x21\x89\x56\x6b\x96\x90\x9d\xe7\x75\xce\x79\xce\xf3\x54\xcf\xeb\x39\xf7\x7d\x9f\xfb\xbe\xfe\xb9\x5e\xd7\x1f\xbf\xef\x76\x7d\xbe\x9f\xdf\xfb\xac\x0d\x17\xb7\x04\xc0\x0f\xf0\x03\x3f\x3f\xc2\x00\x00\x78\xe3\x2e\x23\x50\xbe\x5a\x9e\xfe\xfe\x9e\xa8\xee\x16\xbb\xf6\xf6\x78\x48\x1b\x1b\x52\xee\x70\xd6\x9a\x0d\x6e\x55\xb3\xeb\xb6\x61\xb1\xfb\xce\x80\x35\xba\xed\xda\xb9\xed\xe3\x48\xd6\x06\x82\x17\xa1\x28\x92\x3e\x15\x26\x68\xbd\x56\xb3\xaa\x4e\xd1\xa7\xae\x1d\x75\x9c\x92\x3a\xff\x39\x75\x3f\x6c\x5c\x2a\x3d\xff\xa3\x8f\x8a\xb9\xba\x71\xac\x79\x66\x04\x00\x00\xdb\xdb\x67\x6d\xf8\xf8\xd5\x61\x56\xe3\x0e\x00\x00\xe0\x00\x00\xf8\xff\x6b\x51\x06\x00\x00\x8b\xf3\xc1\x63\xe0\x10\x5f\x54\x20\x12\xef\xa5\xe5\x8d\xc3\x42\x3c\xfd\xfd\x35\x3d\x51\x90\xdf\xab\x83\xf8\xe2\xb4\xb0\x38\x9f\xd0\x14\x66\x66\x85\x9e\x70\x74\x7f\x07\xb4\x0c\x23\xb5\xd2\xae\x18\x7f\xb5\xbf\x50\x6a\xee\xc2\x4d\x6d\xce\x8d\x98\x01\xc7\x0c\xdb\x96\xbe\x65\xc5\x4a\xdd\x83\x3d\xa4\x54\xa9\xb5\xc5\xc9\x8a\xfd\xeb\x32\xaf\xe1\x4d\xc5\x41\x57\x9c\x64\x10\x8d\xa9\x5c\xf6\x3b\x2e\x1b\x3e\xcf\x3e\x1d\xd6\xb2\xff\xee\x5a\x20\x28\xcb\xb2\x2c\xce\x3f\xc7\x80\xaa\x1f\x68\xa5\x58\x7b\xaa\xc0\x39\x79\x73\xeb\x64\xe4\xd7\x24\x3a\xc7\x8d\xd5\x4e\x35\x84\x5e\xef\x48\xbc\x52\x7b\xb8\xf1\xe6\x57\xd0\x2b\xca\x4a\x4f\xb3\x3c\x72\x3b\xb7\xcc\x76\x35\x89\x69\xc0\x79\x85\xee\x2a\x3b\xcc\xb5\x6a\x07\x3f\xf2\xe2\x90\xf8\xa9\x5d\xb7\x91\x1f\x44\x4b\xb4\x03\x1a\xe2\x9e\xdc\xbb\x18\x19\xe7\x26\xa0\x1c\x05\x23\x6a\x5b\x52\x35\x8e\x3c\xd1\x73\x88\x2b\x29\xbe\xdd\x71\x94\x9d\x85\x93\x7b\x90\xa0\x3e\x69\x5a\xd1\x43\x19\xd1\xb7\xa6\x44\x3e\x3b\x28\x47\xe4\x5b\xb3\x53\x8c\xcf\xbb\xe7\x7e\xf0\xb9\x11\x66\x1e\xd2\x20\x4e\x94\x36\xc9\x6a\x73\x91\xa6\x58\xc0\x1f\xe6\xb6\x40\xfb\x0f\x0f\x78\x11\x9c\x36\x70\x33\xae\x1f\x6a\x35\xa1\xf1\x5c\x43\x3b\x92\x24\xe3\xc4\x8f\x19\x1a\x24\x09\xd4\x49\xb8\xa0\xa1\x97\x57\x02\x28\x32\x8e\x05\x87\x0f\xab\x4c\x84\xb5\x24\xab\x75\xb6\x2f\x7b\xdf\x4b\xf5\xf9\x4a\x56\x3c\x92\xf8\x7c\xa4\x8c\x44\x78\x76\xf1\x8d\x45\x34\xc5\xce\x7f\x14\x2b\x3d\x2f\x98\xc3\x4e\x06\xb7\x9f\x39\xf0\xcb\x84\x05\x9e\x28\xbd\x09\xeb\xed\x10\xfd\x40\xab\x4d\x55\x47\xd1\x16\x67\x11\xdc\x86\xf2\xed\x9d\x67\x2a\x77\x4a\x0f\x13\xd7\x45\xdd\x9f\xce\x64\xa6\x68\x7b\xcb\x5e\x36\x0b\x1b\xd0\x10\xb3\x74\x09\x82\xa0\xc6\x10\x27\x99\x87\xf2\xc6\xbc\xc7\x51\xc1\x6f\xe9\x46\xd6\x26\x9a\xd7\x65\x12\xa3\xba\x76\x34\x1d\x5f\x58\xd8\xca\xda\x21\x7e\xa1\x64\xb0\xf6\xe8\x7b\xfa\xde\x91\x49\x4a\xba\x6b\x11\x61\xaf\xbf\x21\xed\x46\xbf\x87\xb1\x08\x51\x2c\x94\xcd\xb9\xb4\xc1\xd2\x7c\xdc\x33\xb5\x5b\x5d\x11\xad\x9c\x92\x92\xaa\xf6\xb5\x01\x26\x38\xe9\x72\xff\x80\x00\x9f\x52\xa9\x30\x23\x61\x8e\xc7\x6f\xb7\x65\x8e\x1d\x76\x92\xee\xd8\x89\x27\x57\xa1\x34\x36\x45\x14\x9e\xef\xe2\xc3\x5f\x54\x53\xae\xb2\xc7\x7b\x54\x3e\x44\x9a\x1f\x1b\xce\x9c\x95\x5e\x30\x90\x07\x88\xa2\x05\x60\xdb\xb7\x64\x4c\x55\x97\x20\x79\xa6\x8e\xc3\x82\xde\xfc\x6c\x62\x28\x91\xd8\x99\xd4\xe2\xc1\x6d\xbc\xd6\x48\x18\xc2\x69\x44\x75\x59\x7e\xce\x45\xf1\x64\xc5\xcb\x98\x55\x37\x83\xee\x9f\x3d\x34\x38\xb6\xb1\x9c\x3e\xaf\xbe\x55\xdc\x03\x1e\x5e\xc8\x1b\x92\xbf\x91\xa1\x65\x1c\x90\x53\xea\x27\x37\x38\x25\x95\xc6\x1b\x78\xb5\x47\x7e\xc6\xda\xd9\x8a\x9a\xd0\xab\x0f\x72\xd1\xc6\xc7\x40\xa1\x2a\x33\xe1\x86\x9b\xe2\x7f\x6a\x97\xf1\x48\xff\x0c\x8a\x13\x00\x6a\x78\xfe\x4a\xbb\x47\xfe\xb1\x76\x03\x82\x2f\x07\x7a\x5e\x83\x04\x04\xfa\x60\x50\x5e\x10\x04\xc6\xf3\xbf\x57\xae\xd8\xa9\x23\x20\xd7\x4a\x82\xc1\xbe\x30\x5f\x10\x2d\xd8\x71\x2b\x3b\xc4\xb7\xd7\x11\x7b\x35\xf1\x2e\xf3\x95\x87\x17\xcc\x05\x95\xaa\xc1\xfd\x88\x43\x57\x3d\x7a\x3b\x6c\x6a\x89\x8f\x1c\x91\xf1\x98\x4e\x00\xd1\xc3\x2f\x30\x96\x89\x4c\xf0\xce\x0f\xc8\x4d\x1e\x40\x61\xb7\xbe\xa8\x0a\x71\xa7\xad\x15\xc9\x7a\xc3\x64\x7e\x80\xcf\x9a\xca\xa7\xdb\x59\xbc\xf3\x30\xae\xa5\xe2\x59\xcb\x45\x5e\x87\x4e\xbb\xeb\x47\x2d\xcb\x15\xea\x19\x3d\x13\xa0\x39\x99\x02\x37\xcb\x18\x55\x66\xd1\x99\xce\x62\x27\xf1\xfe\xc3\xb6\x23\x66\xb7\xb2\x57\x78\x5c\x49\xfb\x22\x6c\x5a\x95\xd6\xaa\x4f\x57\x57\x45\xa8\xad\x3b\xcb\xbe\x3a\xe6\x31\xe9\x7c\x5a\xca\xa7\x3e\xed\x1d\xb7\xe9\x84\xce\x46\xa3\xeb\xc6\xcb\xd9\xb8\x93\xbb\xa7\x3f\x41\xe6\xd7\x74\xb2\xe9\x5a\x73\xcb\x0d\x02\x46\xd7\x47\x57\x11\x23\x3a\x53\x9b\x45\x60\x8d\x61\x70\xba\xfd\x93\x80\x30\xbf\x7e\x94\xcc\x87\xa0\xdc\xc5\x9a\xd4\xcf\x2a\x93\xe2\x28\xc8\x7b\x25\xc2\x27\x15\xbb\xea\xae\x92\xba\x4b\x02\x94\xc9\x93\xda\x15\x52\x54\x61\xa2\x1b\x83\x2b\x2c\x6a\x55\x36\xed\x63\x72\xbd\xde\xe7\xda\xfb\x1b\x0a\xa1\xd7\x8c\xe6\x7f\x15\x5d\x63\xdd\xbf\x35\x32\xc5\x7f\xe2\x75\xfe\xa9\x01\xaf\xc8\x27\xe9\x42\x71\xda\x81\xcc\x18\x9e\x03\x3a\x75\xe2\xed\xf8\xf9\xcc\x66\x31\x76\x3b\x5e\xad\x9a\x1e\x1b\xb0\x24\x64\x0f\x37\x73\xf2\x4e\x36\xf9\x4c\x30\x79\x4f\x10\xf3\x3d\x13\xbc\x45\x46\xf8\xd0\xef\x50\xd8\xe8\x4f\x11\x6b\x95\x76\x93\xdd\x99\x1c\x25\x86\x0a\x95\x93\x6f\xc2\xc6\xdf\x5c\x89\x7c\x9b\xc7\x6e\x36\xb2\xbe\x7b\x5b\x44\x66\x2a\x85\x37\x67\x35\xd4\x2a\x9a\x32\xd1\xab\x4e\xc5\x3f\xf4\xb7\x7e\xb8\x71\xb3\xd6\xd0\x8f\x53\xbe\xe0\x9b\xa5\xe4\xa8\xee\xa2\x74\x1d\xcb\x16\xa4\x78\x57\x48\xcb\x56\x85\x9c\xe3\xc8\xd2\xf6\x4b\x2b\x12\xb0\x9d\x2a\x16\xbf\x63\xe0\x72\x1a\xb5\x47\x63\x96\x36\x3c\x3c\xb7\x94\x7c\xce\x81\xa7\x0d\x3f\x13\x51\x0e\xef\x85\x0b\xc8\x8c\x26\xba\x21\x8e\x7d\x4b\xe7\x4d\x7b\x61\x7d\xde\x02\xc9\xcc\x05\xab\x53\x52\x4f\x13\x25\xb9\xfa\x30\xb1\xe2\x19\x85\x31\x20\xa6\xa7\x66\xc4\x71\x69\xac\xa8\xb5\xd6\x48\x98\x55\x37\x9d\xb7\x44\x90\x3e\xf9\x20\x0f\x7e\xef\xb9\x70\x9a\xea\xbe\x46\x7f\xc8\xb5\x01\x57\x72\x95\x72\x0b\x87\xf2\xee\x8f\x0f\xa9\x4d\xa9\xcb\x24\x3d\xe7\x6c\x03\x38\x6b\xf1\xdb\xcb\xbd\x09\xa7\x58\x06\x5c\xe5\xc4\x58\xca\xa4\x88\x6a\x25\xfa\xe5\x8b\x83\xd1\x7b\xb2\xae\x15\x9f\xfb\xa5\xe8\x0c\xae\xbe\xda\x88\x65\xd1\xaf\x63\xd3\xf6\x70\x8a\x14\x97\x7b\x2f\x16\x19\xe4\x56\xd8\x4b\x9a\x45\x6f\x5b\xf0\xc6\x80\x58\x79\xde\x60\xc7\xd2\x86\xcc\x20\x6b\xa3\x38\xd9\x53\xb1\x8c\xce\x9a\x36\x91\x92\x4a\xb1\x7c\x49\x42\x77\xa0\x08\xfe\x62\x46\xad\x29\x76\x7d\xd5\x1e\x9d\x54\xc4\xd1\x92\x79\x79\xfd\xc6\x4d\x5d\xbc\xcb\xb2\x79\x82\x42\x91\x74\x19\xcd\xfe\x08\x26\x24\xc7\x78\x50\x87\x0d\x57\xa4\x73\x30\xd6\x03\xb3\x51\xc8\xf1\x63\xe7\x5d\x09\x6d\x71\x43\x50\x32\x90\x94\x40\x11\xb4\xdf\x9c\xad\x45\xb0\x69\xac\xb7\x18\x00\x49\xc7\x34\xa8\x97\x57\x03\x3d\x16\x66\x34\x9e\xc2\x5e\x2e\xfa\xbe\xb2\xd4\xc7\x96\x7d\xe8\x18\x7f\x8c\x99\xa7\xcd\x1e\x10\x6e\xc8\x31\x25\xeb\x9a\x2c\x71\x20\x48\x6c\xd3\x83\xda\xd7\x98\xce\x32\xf6\xea\x7e\x5a\x72\x22\xa3\xbd\xde\xd1\xff\x40\xc5\xa1\xe3\x86\x7d\x2a\x0b\xdf\x16\x16\xb0\xe4\xa1\xd9\x49\xf8\x87\xad\xba\xfc\xe5\x13\xfb\x5e\x48\xa6\x82\xc0\x7a\x91\x81\xb6\xaf\x55\xc2\xb1\x30\x67\xf7\x0c\x7b\x58\xce\xc8\xa0\x13\xcb\x7d\x49\xeb\xcd\x06\xef\x0d\x53\x53\x63\xdd\xaa\x5d\xf5\xe6\x3e\x47\x4f\x18\xf0\x3e\x2c\x8f\x30\x26\x31\xdd\xf5\x1c\x7c\x5b\x10\x63\x18\x88\xa5\x52\xab\xaf\x9e\x4d\xd5\x27\xb1\x03\xe3\xd6\xcc\xa8\x57\xd7\xaf\x8b\x2a\xb6\xbb\xc4\xa3\x03\xc2\x7f\xc5\xbe\x5e\xc8\x78\xbf\x54\xe7\x6b\x50\x9a\x3c\xa1\x99\x51\x43\xd3\x2a\x80\xf9\x32\x4f\x43\xce\x59\xfa\x08\xa9\x47\x26\x3d\x23\xbe\x56\xc9\x98\xfa\x86\x8b\xd7\x1e\xe0\x34\xf7\xbd\x6e\x23\xc7\x32\x78\x3c\x7d\x3c\xc4\x09\x92\xe2\x80\x39\x34\x6a\x5a\x16\x29\x3c\x8e\x96\xdd\xd9\x48\x75\x8a\x3e\x18\x66\x6e\x03\xf5\x66\x06\x4b\x30\x12\x09\x83\x83\xa6\xf3\x8f\x29\x02\xe4\xaf\x1b\xbc\xb6\x23\xb9\xa1\x49\x1e\x1b\x84\x61\xf6\xb4\x04\xf1\x39\xfa\x85\xa0\x32\x82\x3e\xf8\x1e\x0a\x77\xd5\x33\x87\xb6\xf7\xc5\xca\x7c\xaa\x43\x63\x3e\xd8\x3c\xf0\x50\x75\x16\x77\x8f\x7c\x37\xc7\xee\xd8\x8f\x46\xde\xbe\x0d\x53\xee\xa0\xcf\xba\x25\x15\x28\x63\x55\x93\xde\xef\x9d\xae\x5c\xcc\x05\x91\xfd\xee\x82\x98\x27\xfd\x6a\xf3\x5f\xda\x18\x85\x4e\x50\x76\x7d\x11\xf1\xdd\x52\x4e\x68\x6e\x74\xb8\xc4\x3b\xce\x4f\x6d\xba\xa9\x2d\x49\xca\x04\x87\x84\x37\x2c\xb1\x6d\x2d\xfa\xa9\x62\xf2\x23\x48\xb0\xfb\x9e\xa0\xb4\xe9\x5a\xd5\x75\xbf\x8f\x86\xf6\xf2\x53\x19\xe9\x3e\x23\x15\x76\xb7\x87\x56\xaf\x56\x08\xe7\x9b\x0c\x70\xef\x66\x52\x13\x46\xaf\xa9\x81\xc3\x22\xaa\x88\x0f\xb4\x06\xad\xbc\x9b\xcd\x63\xa0\xef\x0a\xa6\x16\x74\xcd\x09\x01\x7d\x2e\xfd\x63\x05\x6c\x11\x58\x31\x4d\xae\xe1\x2a\x78\xba\x02\x21\x57\x9f\x68\x48\x1a\x7f\xb5\x66\xc0\xdb\x38\x17\xf2\xae\x4f\x40\xb7\x77\x01\x17\x5c\x58\xba\xcc\x2b\xdb\x9a\x60\x8a\x7c\x8a\x93\x0b\x0e\x5e\xfa\x62\x68\xe9\x1c\x6c\x9c\x5d\x6d\xea\x49\x4a\x7b\xe4\xd5\x89\x63\x88\xa1\x57\x02\x03\xb6\x8d\xc7\x27\x8e\x8c\x7a\xf2\xaf\xbe\xa5\xcf\x15\xa5\x3c\xdd\xc6\x04\xf6\xcb\xd9\x8c\x93\x57\x24\xe1\xc1\x29\xd3\xe1\x45\x01\x15\x01\xc4\x0a\xfd\xce\xe6\x77\x13\xe6\xb4\x10\x0e\xfa\x17\xa2\x84\xd9\x12\xed\xf4\x3d\xe3\xbd\x33\x33\x11\x26\x0d\xfd\xb3\xd3\x6e\x14\xbe\x85\xe0\xcd\x0b\xc7\x4f\xf6\x7c\x7a\x1b\xfc\xed\xee\xb8\x1a\x7a\xdf\x2c\xf3\x06\xc7\x9f\x26\xdc\x18\x91\xad\xa1\xc0\x03\x00\xa6\x12\x7f\x65\xc2\xfa\xff\xaa\x09\xff\xf1\xfa\xc3\x86\x43\x93\xea\x2f\x57\x68\x8b\x46\xcd\x1a\xbd\xb0\xaa\x89\xd8\xa5\x1f\x14\xbe\x69\x5f\xfa\x6c\x39\x83\x19\x29\xe2\x84\x7f\xd5\xe2\x91\x69\x61\xb7\x3e\x9d\xaa\x48\xe9\xb4\x1a\xee\x19\x25\xe1\x82\x06\x46\x29\x15\x62\xe5\x3b\xaa\xa5\x16\xeb\x19\xce\x58\x10\xbf\x2f\x2d\x77\x9e\xa3\xeb\xbe\x70\xe6\xcd\x4b\xe5\x39\x4a\xe0\xe3\x06\x04\xb5\xa6\xcc\xc8\xe8\x0a\x28\xd9\x8f\x78\xcd\x91\x77\xec\xe2\x09\xe1\x42\x09\x22\xb9\x45\xc3\x48\x28\xca\xc3\xce\xcd\x73\x10\xc1\x2b\x65\x35\x25\x77\x45\xc8\x7a\x0c\x3e\x9e\xcd\x51\x6e\xa9\xad\xc3\x9d\xa8\xf0\xad\x9b\xe4\x17\xc4\x82\x19\xdc\x79\xa5\xd7\x07\x7a\x8a\x5c\x19\x50\x34\xb4\xec\x62\xc1\xf6\x00\x47\x95\x5a\xee\x83\xe3\x05\x6c\x4b\x9a\x9e\xbe\x36\x57\x87\x8e\x9a\x16\x4c\x33\x0e\x1a\xa1\x96\x82\xef\xdd\xdc\x5b\xcd\xf7\x31\x8f\x54\x70\xa5\x37\x6d\x60\x76\xa4\xcf\xa4\x35\x29\x14\x74\x6b\x38\x4b\xdb\x2d\x04\x5b\x33\xbc\xaf\x8d\x27\xfd\xb3\xbf\x3f\xe3\x7e\x37\xf2\x5c\x08\xf6\x58\x9c\xf7\x97\x9a\x72\xcd\xaf\x13\x94\xa6\x72\xda\x2d\x92\xd0\x23\x66\x2b\xb5\xb5\xad\xf0\xe3\x0e\xf5\xb3\xe0\xc4\xd2\xa0\xad\x2a\x45\xc8\xcc\xa2\x92\xd1\x96\x79\xa4\xe6\x32\xa1\x78\xa2\x95\x79\xb2\xd0\x29\xd9\xf4\x42\x9e\xc6\x93\xd2\xff\x61\x39\xfc\x55\x40\x69\x3f\x07\x00\xa0\x39\xff\xa3\xf7\x21\x3e\x10\x85\xf9\xe3\x47\x60\x12\xeb\x6f\x45\x69\x8b\x5a\xcc\x6e\x4a\xf2\x84\xd8\xdf\x99\x76\x1f\x84\x38\x1b\x57\x66\x73\x73\x76\x99\xa9\x3a\x47\xc1\xb7\xc2\x62\x75\x74\x85\x6d\xdd\x69\xc1\x4b\x3b\x96\x88\x9a\x9b\xfa\x42\xa5\x28\x42\xfe\x33\xa4\x13\xe3\x92\x40\x57\x99\x06\x5f\x4a\x54\xa6\x5c\xa8\xc3\x47\x2f\x6f\xa7\x0e\xd5\x47\x88\x74\x79\xe3\x13\xe7\xc9\x2c\xd8\x95\x95\xfa\x69\x01\x40\x4b\xa5\x33\xda\x89\x5b\xf7\x51\x8e\x5d\x7c\xfe\x70\x24\x74\x7e\xdb\x42\x93\xc1\xe4\xdf\xbd\xe7\xed\xbb\x83\xcf\xa5\xcf\x1d\x6d\x82\x47\x5d\xad\xbb\x24\x98\x18\x6e\xe6\xbd\xc9\xac\xcb\x1e\xb1\x18\xb3\xcf\x8c\xa5\xcd\xbf\xee\xeb\x31\xfe\xb3\xfb\x18\x15\x83\x1c\x12\x00\x00\xcb\xbf\x93\x2c\x07\xa7\x04\xf0\xbf\xfd\x7f\x4f\xb9\xc2\xff\x67\x22\xdf\x53\xf6\xcf\x27\xbf\x67\x0c\xe5\x1f\x4e\xdd\xfe\x67\x99\xf8\xe7\x90\xdf\x6f\xcc\x91\x1f\x42\x5a\x71\xfd\x6d\x54\xf9\x39\xc9\xf7\x5a\xd0\xff\x21\x49\x07\xff\xbf\xb1\x8a\x3f\xa7\xf9\x7e\xe8\x3f\xf6\xe2\x24\xf8\xb7\x65\x76\xd6\x86\x87\xf7\xb7\x10\x3c\x00\x0f\x50\xc7\x01\x00\x30\xa1\xdf\xbe\xfe\x2b\x00\x00\xff\xff\xb2\x21\x5c\xb0\x2a\x0d\x00\x00")
func syntaxStdlibArraizBytes() ([]byte, error) {
return bindataRead(
@@ -105,8 +105,8 @@ func syntaxStdlibArraiz() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "syntax/stdlib.arraiz", size: 3361, mode: os.FileMode(0644), modTime: time.Unix(1, 0)}
- a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xcc, 0x46, 0xf, 0x83, 0x56, 0xfb, 0x23, 0xb0, 0x5e, 0x6d, 0x49, 0xb0, 0xd8, 0x18, 0x9c, 0x7e, 0xe7, 0x41, 0xe5, 0xc8, 0x99, 0x35, 0x90, 0x24, 0xf9, 0xb7, 0xf, 0x8e, 0xff, 0xe, 0x6c, 0x31}}
+ info := bindataFileInfo{name: "syntax/stdlib.arraiz", size: 3370, mode: os.FileMode(0644), modTime: time.Unix(1, 0)}
+ a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x9f, 0x2f, 0xc6, 0x2, 0x47, 0x55, 0x82, 0x17, 0x25, 0xb1, 0xde, 0x53, 0x41, 0xd5, 0x8e, 0x9d, 0x7f, 0xba, 0x81, 0xd2, 0x91, 0x3b, 0x5e, 0xd0, 0xc8, 0x7, 0xaf, 0x27, 0x49, 0x77, 0x7a, 0x7f}}
return a, nil
}
diff --git a/syntax/bundle.go b/syntax/bundle.go
index 1ec41d20..56b90cd2 100644
--- a/syntax/bundle.go
+++ b/syntax/bundle.go
@@ -13,6 +13,8 @@ import (
"runtime"
"strings"
+ "github.com/spf13/afero"
+
"github.com/anz-bank/pkg/mod"
"github.com/arr-ai/arrai/pkg/ctxfs"
"github.com/arr-ai/arrai/rel"
@@ -98,7 +100,7 @@ func GetMainBundleSource(ctx context.Context) (context.Context, []byte, string)
ctx = withBundledConfig(ctx)
mainFile := bundleToValidPath(ctx, fromBundleConfig(ctx).mainFile)
fs := ctxfs.SourceFsFrom(ctx)
- buf, err := ctxfs.ReadFile(fs, mainFile)
+ buf, err := afero.ReadFile(fs, mainFile)
if err != nil {
panic(fmt.Errorf("not bundled properly, main file not accessible: %s", err))
}
@@ -131,7 +133,7 @@ func withBundledConfig(ctx context.Context) context.Context {
return ctx
}
- buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), BundleConfig)
+ buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), BundleConfig)
if err != nil {
// config file not generated
panic(err)
@@ -200,7 +202,7 @@ func SetupBundle(ctx context.Context, filePath string, source []byte) (_ context
return ctx, createConfig(ctx)
}
- buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(root, ModuleRootSentinel))
+ buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(root, ModuleRootSentinel))
if err != nil {
return ctx, err
}
@@ -239,7 +241,7 @@ func addModuleSentinel(ctx context.Context, rootPath string) (err error) {
rootPath = filepath.Join(rootPath, ModuleRootSentinel)
- buf, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), rootPath)
+ buf, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), rootPath)
if err != nil {
return err
}
@@ -292,7 +294,7 @@ func bundleLocalFile(ctx context.Context, filePath string) (err error) {
filePath += arraiExt
}
- source, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filePath)
+ source, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filePath)
if err != nil {
return err
}
@@ -327,7 +329,7 @@ func bundleModule(ctx context.Context, relImportPath string, m *mod.Module) (con
relImportPath += arraiExt
}
- source, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(m.Dir, relImportPath))
+ source, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filepath.Join(m.Dir, relImportPath))
if err != nil {
return ctx, err
}
diff --git a/syntax/import.go b/syntax/import.go
index 675233fa..7a2d6aab 100644
--- a/syntax/import.go
+++ b/syntax/import.go
@@ -12,6 +12,8 @@ import (
"strings"
"sync"
+ "github.com/spf13/afero"
+
"github.com/anz-bank/pkg/mod"
"github.com/arr-ai/arrai/pkg/ctxfs"
"github.com/arr-ai/arrai/pkg/ctxrootcache"
@@ -259,7 +261,7 @@ func fileValue(ctx context.Context, decoder rel.Tuple, filename string) (rel.Exp
filename += ".arrai"
}
- bytes, err := ctxfs.ReadFile(ctxfs.SourceFsFrom(ctx), filename)
+ bytes, err := afero.ReadFile(ctxfs.SourceFsFrom(ctx), filename)
if err != nil {
return nil, err
}
diff --git a/syntax/std_os_nonwasm.go b/syntax/std_os_nonwasm.go
index 7ecf1841..683c793a 100644
--- a/syntax/std_os_nonwasm.go
+++ b/syntax/std_os_nonwasm.go
@@ -10,6 +10,8 @@ import (
"runtime"
"strings"
+ "github.com/spf13/afero"
+
"github.com/arr-ai/arrai/pkg/arraictx"
"github.com/arr-ai/arrai/pkg/ctxfs"
@@ -75,7 +77,7 @@ func stdOsFile(ctx context.Context, v rel.Value) (rel.Value, error) {
if err != nil {
return nil, err
}
- buf, err := ctxfs.ReadFile(ctxfs.RuntimeFsFrom(ctx), filePath.String())
+ buf, err := afero.ReadFile(ctxfs.RuntimeFsFrom(ctx), filePath.String())
if err != nil {
//TODO: wrap this in an arrai error message
return nil, err
diff --git a/syntax/stdlib/flag_test.arrai b/syntax/stdlib/flag_test.arrai
index afd8a3d7..26c509c5 100644
--- a/syntax/stdlib/flag_test.arrai
+++ b/syntax/stdlib/flag_test.arrai
@@ -140,14 +140,14 @@ let alias = flag.parser({'alias': (type: 'string', alias: 'a')});
'bool': (type: 'bool', alias: 'b'),
'default': (type: 'string', default: 'abc')
});
- {
+ [
'Options:',
'--bool, -b',
'--default string (default: abc)',
'--num, -n number',
'takes a number',
'--str string',
- } => //test.assert.true(
+ ] >> //test.assert.true(
//seq.contains(., help)
)
# TODO: test overlapping alias when errors can be tested