初始提交,未完全测试
This commit is contained in:
283
template/stat/Parser.py
Normal file
283
template/stat/Parser.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/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}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user