Skip to content

Commit

Permalink
Merge branch 'master' into support-deref-in-func
Browse files Browse the repository at this point in the history
  • Loading branch information
ckganesan authored May 18, 2024
2 parents 87ad32c + cae6003 commit d30814e
Show file tree
Hide file tree
Showing 15 changed files with 214 additions and 164 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-versions: [ '1.18', '1.22' ]
go-arch: [ '386' ]
steps:
- uses: actions/checkout@v3
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Build
run: GOARCH=${{ matrix.go-arch }} go build
6 changes: 2 additions & 4 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,7 @@ func (c *compiler) IntegerNode(node *ast.IntegerNode) {
}
c.emitPush(int32(node.Value))
case reflect.Int64:
if node.Value > math.MaxInt64 || node.Value < math.MinInt64 {
panic(fmt.Sprintf("constant %d overflows int64", node.Value))
}
panic(fmt.Sprintf("constant %d overflows int64", node.Value))
c.emitPush(int64(node.Value))
case reflect.Uint:
if node.Value < 0 {
Expand All @@ -365,7 +363,7 @@ func (c *compiler) IntegerNode(node *ast.IntegerNode) {
}
c.emitPush(uint16(node.Value))
case reflect.Uint32:
if node.Value > math.MaxUint32 || node.Value < 0 {
if node.Value < 0 {
panic(fmt.Sprintf("constant %d overflows uint32", node.Value))
}
c.emitPush(uint32(node.Value))
Expand Down
47 changes: 46 additions & 1 deletion expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1623,7 +1623,10 @@ func TestCompile_exposed_error(t *testing.T) {

b, err := json.Marshal(err)
require.NoError(t, err)
require.Equal(t, `{"Line":1,"Column":2,"Message":"invalid operation: == (mismatched types int and bool)","Snippet":"\n | 1 == true\n | ..^","Prev":null}`, string(b))
require.Equal(t,
`{"from":2,"to":4,"line":1,"column":2,"message":"invalid operation: == (mismatched types int and bool)","snippet":"\n | 1 == true\n | ..^","prev":null}`,
string(b),
)
}

func TestAsBool_exposed_error(t *testing.T) {
Expand Down Expand Up @@ -2667,3 +2670,45 @@ func TestIssue_integer_truncated_by_compiler(t *testing.T) {
_, err = expr.Compile("fn(256)", expr.Env(env))
require.Error(t, err)
}

func TestExpr_crash(t *testing.T) {
content, err := os.ReadFile("testdata/crash.txt")
require.NoError(t, err)

_, err = expr.Compile(string(content))
require.Error(t, err)
}

func TestExpr_nil_op_str(t *testing.T) {
// Let's test operators, which do `.(string)` in VM, also check for nil.

var str *string = nil
env := map[string]any{
"nilString": str,
}

tests := []struct{ code string }{
{`nilString == "str"`},
{`nilString contains "str"`},
{`nilString matches "str"`},
{`nilString startsWith "str"`},
{`nilString endsWith "str"`},

{`"str" == nilString`},
{`"str" contains nilString`},
{`"str" matches nilString`},
{`"str" startsWith nilString`},
{`"str" endsWith nilString`},
}

for _, tt := range tests {
t.Run(tt.code, func(t *testing.T) {
program, err := expr.Compile(tt.code)
require.NoError(t, err)

output, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, false, output)
})
}
}
28 changes: 21 additions & 7 deletions file/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,36 @@ import (

type Error struct {
Location
Message string
Snippet string
Prev error
Line int `json:"line"`
Column int `json:"column"`
Message string `json:"message"`
Snippet string `json:"snippet"`
Prev error `json:"prev"`
}

func (e *Error) Error() string {
return e.format()
}

func (e *Error) Bind(source *Source) *Error {
if snippet, found := source.Snippet(e.Location.Line); found {
func (e *Error) Bind(source Source) *Error {
e.Line = 1
for i, r := range source {
if i == e.From {
break
}
if r == '\n' {
e.Line++
e.Column = 0
} else {
e.Column++
}
}
if snippet, found := source.Snippet(e.Line); found {
snippet := strings.Replace(snippet, "\t", " ", -1)
srcLine := "\n | " + snippet
var bytes = []byte(snippet)
var indLine = "\n | "
for i := 0; i < e.Location.Column && len(bytes) > 0; i++ {
for i := 0; i < e.Column && len(bytes) > 0; i++ {
_, sz := utf8.DecodeRune(bytes)
bytes = bytes[sz:]
if sz > 1 {
Expand Down Expand Up @@ -54,7 +68,7 @@ func (e *Error) Wrap(err error) {
}

func (e *Error) format() string {
if e.Location.Empty() {
if e.Snippet == "" {
return e.Message
}
return fmt.Sprintf(
Expand Down
8 changes: 2 additions & 6 deletions file/location.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package file

type Location struct {
Line int // The 1-based line of the location.
Column int // The 0-based column number of the location.
}

func (l Location) Empty() bool {
return l.Column == 0 && l.Line == 0
From int `json:"from"`
To int `json:"to"`
}
73 changes: 21 additions & 52 deletions file/source.go
Original file line number Diff line number Diff line change
@@ -1,78 +1,47 @@
package file

import (
"encoding/json"
"strings"
"unicode/utf8"
)

type Source struct {
contents []rune
lineOffsets []int32
}

func NewSource(contents string) *Source {
s := &Source{
contents: []rune(contents),
}
s.updateOffsets()
return s
}

func (s *Source) MarshalJSON() ([]byte, error) {
return json.Marshal(s.contents)
}

func (s *Source) UnmarshalJSON(b []byte) error {
contents := make([]rune, 0)
err := json.Unmarshal(b, &contents)
if err != nil {
return err
}
type Source []rune

s.contents = contents
s.updateOffsets()
return nil
func NewSource(contents string) Source {
return []rune(contents)
}

func (s *Source) Content() string {
return string(s.contents)
func (s Source) String() string {
return string(s)
}

func (s *Source) Snippet(line int) (string, bool) {
func (s Source) Snippet(line int) (string, bool) {
if s == nil {
return "", false
}
charStart, found := s.findLineOffset(line)
if !found || len(s.contents) == 0 {
lines := strings.Split(string(s), "\n")
lineOffsets := make([]int, len(lines))
var offset int
for i, line := range lines {
offset = offset + utf8.RuneCountInString(line) + 1
lineOffsets[i] = offset
}
charStart, found := getLineOffset(lineOffsets, line)
if !found || len(s) == 0 {
return "", false
}
charEnd, found := s.findLineOffset(line + 1)
charEnd, found := getLineOffset(lineOffsets, line+1)
if found {
return string(s.contents[charStart : charEnd-1]), true
}
return string(s.contents[charStart:]), true
}

// updateOffsets compute line offsets up front as they are referred to frequently.
func (s *Source) updateOffsets() {
lines := strings.Split(string(s.contents), "\n")
offsets := make([]int32, len(lines))
var offset int32
for i, line := range lines {
offset = offset + int32(utf8.RuneCountInString(line)) + 1
offsets[int32(i)] = offset
return string(s[charStart : charEnd-1]), true
}
s.lineOffsets = offsets
return string(s[charStart:]), true
}

// findLineOffset returns the offset where the (1-indexed) line begins,
// or false if line doesn't exist.
func (s *Source) findLineOffset(line int) (int32, bool) {
func getLineOffset(lineOffsets []int, line int) (int, bool) {
if line == 1 {
return 0, true
} else if line > 1 && line <= len(s.lineOffsets) {
offset := s.lineOffsets[line-2]
} else if line > 1 && line <= len(lineOffsets) {
offset := lineOffsets[line-2]
return offset, true
}
return -1, false
Expand Down
15 changes: 0 additions & 15 deletions file/source_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package file

import (
"encoding/json"
"testing"

"github.com/expr-lang/expr/internal/testify/assert"
)

const (
Expand Down Expand Up @@ -55,15 +52,3 @@ func TestStringSource_SnippetSingleLine(t *testing.T) {
t.Errorf(unexpectedSnippet, t.Name(), str2, "")
}
}

func TestStringSource_MarshalJSON(t *testing.T) {
source := NewSource("hello, world")
encoded, err := json.Marshal(source)
assert.NoError(t, err)
assert.Equal(t, `[104,101,108,108,111,44,32,119,111,114,108,100]`, string(encoded))

decoded := &Source{}
err = json.Unmarshal(encoded, decoded)
assert.NoError(t, err)
assert.Equal(t, source.Content(), decoded.Content())
}
Loading

0 comments on commit d30814e

Please sign in to comment.