Skip to content

Commit

Permalink
Extend string templates to support basic expression with no nested br…
Browse files Browse the repository at this point in the history
…aces.
  • Loading branch information
RZhang05 committed Sep 20, 2024
1 parent 99f4ae4 commit 3be11cf
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 15 deletions.
15 changes: 13 additions & 2 deletions runtime/parser/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,8 @@ func defineStringExpression() {
missingEnd := true

for curToken.Is(lexer.TokenString) {
// this loop alternates between parsing a string and then a string template
// it is expected that any valid StringTemplateExpression will end with a string
literal = p.tokenSource(curToken)
length = len(literal)

Expand All @@ -1184,12 +1186,21 @@ func defineStringExpression() {
// parser already points to next token
curToken = p.current
if curToken.Is(lexer.TokenStringTemplate) {
// move on to what is after the $
p.next()
// advance to the expression
if !p.current.Is(lexer.TokenIdentifier) {

// check if $identifier or ${expression}
var isCurly = p.current.Is(lexer.TokenBraceOpen)
if !isCurly && !p.current.Is(lexer.TokenIdentifier) {
return nil, p.syntaxError("expected an identifier got: %s", p.currentTokenSource())
}
if isCurly {
p.next() // move on to expression
}
value, err := parseExpression(p, lowestBindingPower)
if isCurly {
_, err = p.mustOne(lexer.TokenBraceClose)
}
if err != nil {
return nil, err
}
Expand Down
45 changes: 45 additions & 0 deletions runtime/parser/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6224,6 +6224,51 @@ func TestParseStringTemplate(t *testing.T) {
errs,
)
})

t.Run("valid, expression", func(t *testing.T) {

t.Parallel()

_, errs := testParseExpression(`
"2+2 = ${2+2}"
`)

var err error
if len(errs) > 0 {
err = Error{
Errors: errs,
}
}

require.NoError(t, err)
})

t.Run("invalid, missing brace", func(t *testing.T) {

t.Parallel()

_, errs := testParseExpression(`
"2+2 = ${2+2"
`)

var err error
if len(errs) > 0 {
err = Error{
Errors: errs,
}
}

require.Error(t, err)
utils.AssertEqualWithDiff(t,
[]error{
&SyntaxError{
Message: "expected token '}'",
Pos: ast.Position{Offset: 17, Line: 2, Column: 16},
},
},
errs,
)
})
}

func TestParseNilCoalescing(t *testing.T) {
Expand Down
77 changes: 77 additions & 0 deletions runtime/parser/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,83 @@ func TestLexString(t *testing.T) {
)
})

t.Run("valid, str expr template", func(t *testing.T) {
testLex(t,
`"${abc}.length"`,
[]token{
{
Token: Token{
Type: TokenString,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 0, Offset: 0},
EndPos: ast.Position{Line: 1, Column: 0, Offset: 0},
},
},
Source: `"`,
},
{
Token: Token{
Type: TokenStringTemplate,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 1, Offset: 1},
EndPos: ast.Position{Line: 1, Column: 1, Offset: 1},
},
},
Source: `$`,
},
{
Token: Token{
Type: TokenBraceOpen,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 2, Offset: 2},
EndPos: ast.Position{Line: 1, Column: 2, Offset: 2},
},
},
Source: `{`,
},
{
Token: Token{
Type: TokenIdentifier,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 3, Offset: 3},
EndPos: ast.Position{Line: 1, Column: 5, Offset: 5},
},
},
Source: `abc`,
},
{
Token: Token{
Type: TokenBraceClose,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 6, Offset: 6},
EndPos: ast.Position{Line: 1, Column: 6, Offset: 6},
},
},
Source: `}`,
},
{
Token: Token{
Type: TokenString,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 7, Offset: 7},
EndPos: ast.Position{Line: 1, Column: 14, Offset: 14},
},
},
Source: `.length"`,
},
{
Token: Token{
Type: TokenEOF,
Range: ast.Range{
StartPos: ast.Position{Line: 1, Column: 15, Offset: 15},
EndPos: ast.Position{Line: 1, Column: 15, Offset: 15},
},
},
},
},
)
})

t.Run("invalid, empty, not terminated at line end", func(t *testing.T) {
testLex(t,
"\"\n",
Expand Down
9 changes: 9 additions & 0 deletions runtime/parser/lexer/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,17 @@ func rootState(l *lexer) stateFn {
l.emitType(TokenParenClose)
case '{':
l.emitType(TokenBraceOpen)
if l.mode == STR_IDENTIFIER {
l.mode = STR_EXPRESSION
} else {
return l.error(fmt.Errorf("string template cannot contain {"))
}
case '}':
l.emitType(TokenBraceClose)
if l.mode == STR_EXPRESSION {
l.mode = NORMAL
return stringState
}
case '[':
l.emitType(TokenBracketOpen)
case ']':
Expand Down
75 changes: 62 additions & 13 deletions runtime/tests/interpreter/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12348,10 +12348,10 @@ func TestInterpretStringTemplates(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
access(all)
struct SomeStruct {}
let a = SomeStruct()
let x: String = "$a"
access(all)
struct SomeStruct {}
let a = SomeStruct()
let x: String = "$a"
`)

AssertValuesEqual(
Expand All @@ -12366,10 +12366,10 @@ func TestInterpretStringTemplates(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
let add = fun(): Int {
return 2+2
}
let x: String = "$add()"
let add = fun(): Int {
return 2+2
}
let x: String = "$add()"
`)

AssertValuesEqual(
Expand All @@ -12384,11 +12384,26 @@ func TestInterpretStringTemplates(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
let add = fun(): Int {
return 2+2
}
let y = add()
let x: String = "$y"
let add = fun(): Int {
return 2+2
}
let y = add()
let x: String = "$y"
`)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredStringValue("4"),
inter.Globals.Get("x").GetValue(inter),
)
})

t.Run("expression", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
let x: String = "${2+2}"
`)

AssertValuesEqual(
Expand All @@ -12398,4 +12413,38 @@ func TestInterpretStringTemplates(t *testing.T) {
inter.Globals.Get("x").GetValue(inter),
)
})

t.Run("expr func", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
let add = fun(): Int {
return 2+2
}
let x: String = "${add()}"
`)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredStringValue("4"),
inter.Globals.Get("x").GetValue(inter),
)
})

t.Run("simple expr", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
let y: String = "abcde"
let x: String = "$y.length = ${y.length}"
`)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredStringValue("abcde.length = 5"),
inter.Globals.Get("x").GetValue(inter),
)
})
}

0 comments on commit 3be11cf

Please sign in to comment.