Files
py_enjoy/template/stat/Parser.py
2026-02-27 14:37:10 +08:00

284 lines
10 KiB
Python

#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Parser - Template Parser
"""
from typing import Optional
from .ast.Stat import Stat, StatList
from ..Env import Env
class Parser:
"""Template parser for parsing template files"""
def __init__(self, env: Env, content: str, name: Optional[str] = None):
"""
Initialize parser
Args:
env: Template environment
content: Template content
name: Template name (optional)
"""
self._env = env
self._content = content
self._name = name
def parse(self) -> Stat:
"""
Parse template content
Returns:
Root statement
"""
from .ast.Text import Text
from .ast.Output import Output
from .ast.For import For
stat_list = StatList()
content = self._content
length = len(content)
pos = 0
while pos < length:
# Find the next # character
next_hash = content.find("#", pos)
if next_hash == -1:
# No more # characters, add the remaining text
if pos < length:
stat_list.add_stat(Text(content[pos:]))
break
# Add text before the # character
if next_hash > pos:
stat_list.add_stat(Text(content[pos:next_hash]))
# Check what follows the #
if next_hash + 1 < length:
next_char = content[next_hash + 1]
# Handle #( expression
if next_char == "(":
# Find the end of the expression
end = self._find_matching_parenthesis(content, next_hash + 2)
if end != -1:
# Extract the expression content
expr_content = content[next_hash + 2:end]
# Create a simple expression list for evaluation
expr_list = SimpleExprList(expr_content)
# Add output stat
stat_list.add_stat(Output(expr_list))
# Move to next position
pos = end + 1
continue
# Check for #for directive
elif next_char == "f" and next_hash + 4 <= length and content[next_hash:next_hash + 4] == "#for":
# Handle #for directive with optional parentheses and different syntax
for_start = next_hash + 4
# Skip whitespace
while for_start < length and content[for_start].isspace():
for_start += 1
# Check if #for is followed by (
if for_start < length and content[for_start] == "(":
# Find the end of the parentheses
for_paren_end = self._find_matching_parenthesis(content, for_start + 1)
if for_paren_end != -1:
# Parse for content inside parentheses
for_content = content[for_start + 1:for_paren_end].strip()
for_end = for_paren_end + 1
else:
# No matching parenthesis, treat as text
stat_list.add_stat(Text(content[next_hash:next_hash + 4]))
pos = next_hash + 4
continue
else:
# Find the end of the #for line
for_end = content.find("\n", next_hash)
if for_end == -1:
for_end = length
# Parse for content
for_content = content[for_start:for_end].strip()
# Handle both "item in items" and "x : listAaa" syntax
if " in " in for_content:
# Python style: for item in items
parts = for_content.split(" in ")
var_name = parts[0].strip()
iter_expr = parts[1].strip()
elif " : " in for_content:
# Java style: for x : listAaa
parts = for_content.split(" : ")
var_name = parts[0].strip()
iter_expr = parts[1].strip()
else:
# Invalid for syntax, treat as text
stat_list.add_stat(Text(content[next_hash:for_end]))
pos = for_end
continue
# Find the matching #end directive
end_pos = self._find_matching_end(content, for_end)
if end_pos != -1:
# Extract the loop body content
body_content = content[for_end:end_pos]
# Parse the loop body
body_parser = Parser(self._env, body_content, self._name)
body_stat = body_parser.parse()
# Create For directive
for_directive = For(var_name, iter_expr, body_stat)
# Add for directive to stat list
stat_list.add_stat(for_directive)
# Move to position after #end directive
pos = end_pos + 4 # Skip "#end"
continue
# If we get here, it's just a # character, add as text
stat_list.add_stat(Text("#"))
pos = next_hash + 1
return stat_list
def _find_matching_end(self, content: str, start: int) -> int:
"""
Find matching #end directive
Args:
content: Content string
start: Start position
Returns:
Position of matching #end directive or -1 if not found
"""
pos = start
length = len(content)
depth = 1 # Start with 1 for the current #for
while pos < length:
# Find the next # character
next_hash = content.find("#", pos)
if next_hash == -1:
break
# Check for #for (increase depth)
if next_hash + 5 <= length and content[next_hash:next_hash + 5] == "#for ":
depth += 1
pos = next_hash + 5
# Check for #end (decrease depth)
elif next_hash + 4 <= length and content[next_hash:next_hash + 4] == "#end":
depth -= 1
if depth == 0:
return next_hash
pos = next_hash + 4
# Otherwise, move past this #
else:
pos = next_hash + 1
return -1
def _find_matching_parenthesis(self, content: str, start: int) -> int:
"""
Find matching parenthesis
Args:
content: Content string
start: Start position
Returns:
Position of matching parenthesis or -1 if not found
"""
count = 1
pos = start
length = len(content)
while pos < length and count > 0:
char = content[pos]
if char == '(':
count += 1
elif char == ')':
count -= 1
if count == 0:
return pos
pos += 1
return -1
class SimpleExprList:
"""
Simple expression list implementation for evaluating template expressions
"""
def __init__(self, expr_str: str):
"""
Initialize simple expression list
Args:
expr_str: Expression string
"""
self._expr_str = expr_str
def eval(self, scope: 'Scope') -> any:
"""
Evaluate expression in the given scope
Args:
scope: Execution scope
Returns:
Evaluation result
"""
try:
# Create a local context with scope variables
local_vars = scope._data.copy()
# Create a special dict wrapper that allows attribute access
def wrap_dict(d):
"""Wrap a dict to allow attribute access"""
if isinstance(d, dict):
# Create a wrapper that allows both dot access and bracket access
class DictWrapper:
def __init__(self, data):
self.__dict__ = data
def __getitem__(self, key):
return data[key]
return DictWrapper(d)
return d
# Wrap all dict values in the local vars
wrapped_vars = {}
for key, value in local_vars.items():
if isinstance(value, dict):
wrapped_vars[key] = wrap_dict(value)
elif isinstance(value, list):
# Wrap dictionaries in lists
wrapped_vars[key] = [wrap_dict(item) for item in value]
else:
wrapped_vars[key] = value
# Handle special case for 'for' variable which is a keyword in Python
expr_str = self._expr_str
if 'for' in wrapped_vars:
# Create a wrapper to avoid keyword conflict
wrapped_vars['_for'] = wrapped_vars['for']
# Replace 'for.' with '_for.' in the expression
expr_str = expr_str.replace('for.', '_for.')
# Evaluate the expression
return eval(expr_str, {}, wrapped_vars)
except Exception as e:
# Handle evaluation errors gracefully
return f"Error evaluating expression '{self._expr_str}': {e}"