Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions liquid/builtin/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .path import Location
from .path import Path
from .path import Segments
from .primary import parse_primary
from .primitive import Identifier
from .primitive import Literal
from .primitive import Nil
Expand All @@ -32,6 +33,7 @@
"Nil",
"parse_arguments",
"parse_identifier",
"parse_primary",
"parse_primitive",
"parse_string_or_path",
"Path",
Expand Down
14 changes: 7 additions & 7 deletions liquid/builtin/expressions/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from liquid.token import TOKEN_EOF
from liquid.token import TOKEN_WORD

from .primitive import parse_primitive
from .primary import parse_primary

if TYPE_CHECKING:
from liquid import Environment
Expand Down Expand Up @@ -65,7 +65,7 @@ def parse(env: Environment, tokens: TokenStream) -> list[KeywordArgument]:

if token.kind == TOKEN_WORD:
tokens.eat_one_of(*argument_separators)
value = parse_primitive(env, tokens)
value = parse_primary(env, tokens)
args.append(KeywordArgument(token, token.value, value))
if env.mode == Mode.STRICT and tokens.current.kind == TOKEN_WORD:
raise LiquidSyntaxError(
Expand Down Expand Up @@ -114,7 +114,7 @@ def parse(env: Environment, tokens: TokenStream) -> list[PositionalArgument]:
if tokens.current.kind == TOKEN_EOF:
break

args.append(PositionalArgument(parse_primitive(env, tokens)))
args.append(PositionalArgument(parse_primary(env, tokens)))

return args

Expand Down Expand Up @@ -154,7 +154,7 @@ def parse(env: Environment, tokens: TokenStream) -> dict[str, Parameter]:
if tokens.current.kind in argument_separators:
# A parameter with a default value
next(tokens) # Move past ":" or "="
value = parse_primitive(env, tokens)
value = parse_primary(env, tokens)
params[token.value] = Parameter(token, token.value, value)
else:
params[token.value] = Parameter(token, token.value, None)
Expand Down Expand Up @@ -191,13 +191,13 @@ def parse_arguments(
if tokens.peek.kind in argument_separators:
name_token = next(tokens)
next(tokens) # = or :
value = parse_primitive(env, tokens)
value = parse_primary(env, tokens)
kwargs.append(KeywordArgument(name_token, token.value, value))
else:
args.append(PositionalArgument(parse_primitive(env, tokens)))
args.append(PositionalArgument(parse_primary(env, tokens)))
else:
# A primitive as a positional argument
args.append(PositionalArgument(parse_primitive(env, tokens)))
args.append(PositionalArgument(parse_primary(env, tokens)))

if tokens.current.kind != TOKEN_COMMA:
break
Expand Down
14 changes: 6 additions & 8 deletions liquid/builtin/expressions/filtered.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .arguments import KeywordArgument
from .arguments import PositionalArgument
from .logical import BooleanExpression
from .primitive import parse_primitive
from .primary import parse_primary

if TYPE_CHECKING:
from liquid import Environment
Expand Down Expand Up @@ -107,7 +107,7 @@ def parse(
env: Environment, tokens: TokenStream
) -> Union[FilteredExpression, TernaryFilteredExpression]:
"""Parse a filtered expression from _tokens_."""
left = parse_primitive(env, tokens)
left = parse_primary(env, tokens)
filters = Filter.parse(env, tokens, delim=(TOKEN_PIPE,))

if tokens.current.kind == TOKEN_IF:
Expand Down Expand Up @@ -221,7 +221,7 @@ def parse(

if tokens.current.kind == TOKEN_ELSE:
next(tokens) # else
alternative = parse_primitive(env, tokens)
alternative = parse_primary(env, tokens)

if tokens.current.kind == TOKEN_PIPE:
filters = Filter.parse(env, tokens, delim=(TOKEN_PIPE,))
Expand Down Expand Up @@ -348,12 +348,10 @@ def parse(
next(tokens) # word
next(tokens) # : or =
args.append(
KeywordArgument(
tok, tok.value, parse_primitive(env, tokens)
)
KeywordArgument(tok, tok.value, parse_primary(env, tokens))
)
else:
args.append(PositionalArgument(parse_primitive(env, tokens)))
args.append(PositionalArgument(parse_primary(env, tokens)))

if tokens.current.kind in FILTER_TOKENS:
raise LiquidSyntaxError(
Expand All @@ -362,7 +360,7 @@ def parse(
token=tokens.current,
)
elif tok.kind in FILTER_TOKENS:
args.append(PositionalArgument(parse_primitive(env, tokens)))
args.append(PositionalArgument(parse_primary(env, tokens)))

# There should be a comma between filter tokens.
if tokens.current.kind in FILTER_TOKENS:
Expand Down
45 changes: 17 additions & 28 deletions liquid/builtin/expressions/logical.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,12 @@ def __str__(self) -> str:
return f"{self.left} and {self.right}"

def evaluate(self, context: RenderContext) -> object:
return is_truthy(self.left.evaluate(context)) and is_truthy(
self.right.evaluate(context)
)
left = self.left.evaluate(context)
return self.right.evaluate(context) if is_truthy(left) else left

async def evaluate_async(self, context: RenderContext) -> object:
return is_truthy(await self.left.evaluate_async(context)) and is_truthy(
await self.right.evaluate_async(context)
)
left = await self.left.evaluate_async(context)
return await self.right.evaluate_async(context) if is_truthy(left) else left

def children(self) -> list[Expression]:
return [self.left, self.right]
Expand All @@ -235,14 +233,12 @@ def __str__(self) -> str:
return f"{self.left} or {self.right}"

def evaluate(self, context: RenderContext) -> object:
return is_truthy(self.left.evaluate(context)) or is_truthy(
self.right.evaluate(context)
)
left = self.left.evaluate(context)
return left if is_truthy(left) else self.right.evaluate(context)

async def evaluate_async(self, context: RenderContext) -> object:
return is_truthy(await self.left.evaluate_async(context)) or is_truthy(
await self.right.evaluate_async(context)
)
left = await self.left.evaluate_async(context)
return left if is_truthy(left) else await self.right.evaluate_async(context)

def children(self) -> list[Expression]:
return [self.left, self.right]
Expand Down Expand Up @@ -436,31 +432,24 @@ def parse_boolean_primitive( # noqa: PLR0912
kind = token.kind

if kind == TOKEN_TRUE:
left = TrueLiteral(token)
next(tokens)
left = TrueLiteral(next(tokens))
elif kind == TOKEN_FALSE:
left = FalseLiteral(token)
next(tokens)
left = FalseLiteral(next(tokens))

elif kind in (TOKEN_NIL, TOKEN_NULL):
left = Nil(token)
next(tokens)
left = Nil(next(tokens))
elif kind == TOKEN_INTEGER:
left = IntegerLiteral(token, to_int(token.value))
next(tokens)
left = IntegerLiteral(next(tokens), to_int(token.value))
elif kind == TOKEN_FLOAT:
left = FloatLiteral(token, float(token.value))
next(tokens)
left = FloatLiteral(next(tokens), float(token.value))
elif kind == TOKEN_STRING:
left = StringLiteral(token, token.value)
next(tokens)
left = StringLiteral(next(tokens), token.value)
elif kind == TOKEN_RANGE_LITERAL:
left = RangeLiteral.parse(env, tokens)
elif kind == TOKEN_BLANK:
left = Blank(token)
next(tokens)
left = Blank(next(tokens))
elif kind == TOKEN_EMPTY:
left = Empty(token)
next(tokens)
left = Empty(next(tokens))
elif kind in (TOKEN_WORD, TOKEN_IDENTSTRING, TOKEN_LBRACKET):
left = Path.parse(env, tokens)
elif kind == TOKEN_LPAREN:
Expand Down
10 changes: 5 additions & 5 deletions liquid/builtin/expressions/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
from liquid.token import TOKEN_OFFSET
from liquid.token import TOKEN_REVERSED

from .primary import parse_primary
from .primitive import StringLiteral
from .primitive import parse_identifier
from .primitive import parse_primitive

if TYPE_CHECKING:
from liquid import Environment
Expand Down Expand Up @@ -216,7 +216,7 @@ def parse(env: Environment, tokens: TokenStream) -> LoopExpression:
token = tokens.current
identifier = parse_identifier(env, tokens)
tokens.eat(TOKEN_IN)
iterable = parse_primitive(env, tokens)
iterable = parse_primary(env, tokens)

reversed_ = False
offset: Expression | None = None
Expand All @@ -237,7 +237,7 @@ def parse(env: Environment, tokens: TokenStream) -> LoopExpression:

if kind == TOKEN_LIMIT:
tokens.eat_one_of(*argument_separators)
limit = parse_primitive(env, tokens)
limit = parse_primary(env, tokens)
elif kind == TOKEN_REVERSED:
reversed_ = True
elif kind == TOKEN_OFFSET:
Expand All @@ -247,10 +247,10 @@ def parse(env: Environment, tokens: TokenStream) -> LoopExpression:
next(tokens)
offset = StringLiteral(token=offset_token, value="continue")
else:
offset = parse_primitive(env, tokens)
offset = parse_primary(env, tokens)
elif kind == TOKEN_COLS:
tokens.eat_one_of(*argument_separators)
cols = parse_primitive(env, tokens)
cols = parse_primary(env, tokens)
elif kind == TOKEN_COMMA:
if env.mode == Mode.STRICT and tokens.peek.kind == TOKEN_COMMA:
raise LiquidSyntaxError(
Expand Down
10 changes: 10 additions & 0 deletions liquid/builtin/expressions/primary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Built-in primary expressions.

A primary expression is any in line logical expression or primitive.
"""

from .logical import parse_boolean_primitive

parse_primary = parse_boolean_primitive

__all__ = ("parse_primary",)
3 changes: 2 additions & 1 deletion liquid/builtin/tags/case_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from liquid.ast import BlockNode
from liquid.ast import Node
from liquid.builtin.expressions import parse_primary
from liquid.builtin.expressions import parse_primitive
from liquid.builtin.expressions.logical import _eq
from liquid.exceptions import LiquidSyntaxError
Expand Down Expand Up @@ -140,7 +141,7 @@ def parse(self, stream: TokenStream) -> Node:
"""Parse tokens from _stream_ into an AST node."""
token = stream.eat(TOKEN_TAG)
tokens = stream.into_inner(tag=token)
left = parse_primitive(self.env, tokens)
left = parse_primary(self.env, tokens)
tokens.expect_eos()

# Eat whitespace or junk between `case` and when/else/endcase
Expand Down
4 changes: 2 additions & 2 deletions liquid/builtin/tags/cycle_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from liquid.ast import Node
from liquid.builtin.expressions import PositionalArgument
from liquid.builtin.expressions import parse_primitive
from liquid.builtin.expressions import parse_primary
from liquid.stringify import to_liquid_string
from liquid.tag import Tag
from liquid.token import TOKEN_COLON
Expand Down Expand Up @@ -120,7 +120,7 @@ def parse(self, stream: TokenStream) -> CycleNode:

group_name: Optional[Expression] = None
if tokens.peek.kind == TOKEN_COLON:
group_name = parse_primitive(self.env, tokens)
group_name = parse_primary(self.env, tokens)
tokens.eat(TOKEN_COLON)

args = PositionalArgument.parse(self.env, tokens)
Expand Down
2 changes: 1 addition & 1 deletion liquid/builtin/tags/render_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def parse(self, stream: TokenStream) -> Node:
# This is the name of the template to be included.
tokens.expect(TOKEN_STRING)
name = parse_primitive(self.env, tokens)
assert isinstance(name, StringLiteral)
assert isinstance(name, StringLiteral) # TODO: better error

alias: Optional[Identifier] = None
var: Optional[Path] = None
Expand Down
11 changes: 11 additions & 0 deletions liquid/golden/case_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@
expect="bar",
globals={"title": "Hello"},
),
Case(
description="or separated when expression, duplicated",
template=(
r"{% case title %}"
r"{% when 'foo' %}foo"
r"{% when title or 'Hello' %}bar"
r"{% endcase %}"
),
expect="barbar",
globals={"title": "Hello"},
),
Case(
description="mix or and comma separated when expression",
template=(
Expand Down
60 changes: 60 additions & 0 deletions liquid/golden/output_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,64 @@
expect="",
globals={"a": [1, 2, 3]},
),
Case(
description="logical or, left is nil",
template="{{ a or b }}",
expect="foo",
globals={"a": None, "b": "foo"},
),
Case(
description="logical or, left is a string",
template="{{ a or b }}",
expect="foo",
globals={"a": "foo", "b": "bar"},
),
Case(
description="logical or, left is an empty string",
template="{{ a or b }}",
expect="",
globals={"a": "", "b": "bar"},
),
Case(
description="logical or, left and right are nil",
template="{{ a or b }}",
expect="",
globals={"a": None, "b": None},
),
Case(
description="logical and, left is nil",
template="{{ a and b }}",
expect="",
globals={"a": None, "b": "foo"},
),
Case(
description="logical and, left is a string",
template="{{ a and b }}",
expect="bar",
globals={"a": "foo", "b": "bar"},
),
Case(
description="logical and, left is an empty string",
template="{{ a and b }}",
expect="bar",
globals={"a": "", "b": "bar"},
),
Case(
description="logical and, left and right are nil",
template="{{ a and b }}",
expect="",
globals={"a": None, "b": None},
),
Case(
description="comparison expression",
template="{{ a < 5 }}",
expect="true",
globals={"a": 2},
),
Case(
description="comparison and logical expression",
template="{{ a < 5 and false }}",
expect="false",
globals={"a": 2},
),
]
12 changes: 12 additions & 0 deletions liquid/golden/plus_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,16 @@
template=r"{{ 10 | plus: -2 }}",
expect="8",
),
Case(
description="argument is a logical expression",
template=r"{{ 10 | plus: a or 1 }}",
expect="11",
globals={"a": None},
),
Case(
description="argument is a logical expression",
template=r"{{ a or 10 | plus: b or 1 }}",
expect="47",
globals={"a": 42, "b": 5},
),
]