- Refactored expression lexers. New, subtly different, tag expression tokenizers are now in `liquid.expressions`. Built-in tags use these lexers indirectly via new specialized expression parsers. Older expression lexers and parsers will be maintained until at least Python Liquid version 2.0 for those that use them in custom tags. See 42.
- Specialized expression parsers. Each of the three built-in expression types now have a dedicated parser defined in `liquid.expressions`, whereas before all expression parsing went through `liquid.parse.ExpressionParser.parse_expression()`. Built-in tags now use these new parsers. The more general parser will be maintained until at least Python Liquid Version 2.0. See 42.
- `liquid.parse.Parser.parse_block()` now accepts any container as its `end` argument. Benchmarks show that using a `frozenset` for `end` instead of a tuple gives a small performance improvement.
- Fixed an incompatibility with the reference implementation where Python Liquid would not recognize identifiers with a trailing question mark. This seems to be a common idiom in Ruby to indicate something returns a Boolean value.
- Added `get_source_with_context()` and `get_source_with_context_async()` to `liquid.loaders.BaseLoader`. Custom loaders can now use the active render context to dynamically modify their search space when used from `include` or `render`, or any custom tag using `Context.get_template_with_context()`.
`Context.get_template_with_context()` also accepts arbitrary keyword arguments that are passed along to `get_source_with_context()`. The build-in `include` and `render` tags add a `tag` argument with their tag name, so custom loaders can modify their search space depending on which tag was used.
See the [Custom Loaders](https://jg-rp.github.io/liquid/guides/custom-loaders) documentation for examples.