初始提交,未完全测试
This commit is contained in:
45
template/stat/Compressor.py
Normal file
45
template/stat/Compressor.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Compressor - Template Compressor
|
||||
"""
|
||||
|
||||
class Compressor:
|
||||
"""Template compressor for minimizing output"""
|
||||
|
||||
def __init__(self, separator: str = '\n'):
|
||||
"""
|
||||
Initialize compressor
|
||||
|
||||
Args:
|
||||
separator: Separator string
|
||||
"""
|
||||
self._separator = separator
|
||||
|
||||
def compress(self, content: str) -> str:
|
||||
"""
|
||||
Compress template content
|
||||
|
||||
Args:
|
||||
content: Content to compress
|
||||
|
||||
Returns:
|
||||
Compressed content
|
||||
"""
|
||||
# Simplified compression
|
||||
if not content:
|
||||
return content
|
||||
|
||||
# Remove extra whitespace
|
||||
lines = content.split(self._separator)
|
||||
compressed_lines = []
|
||||
|
||||
for line in lines:
|
||||
compressed_line = line.strip()
|
||||
if compressed_line:
|
||||
compressed_lines.append(compressed_line)
|
||||
|
||||
return self._separator.join(compressed_lines)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Compressor(separator={repr(self._separator)})"
|
||||
35
template/stat/OutputDirectiveFactory.py
Normal file
35
template/stat/OutputDirectiveFactory.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal OutputDirectiveFactory - Output Directive Factory
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from .ast.Stat import Stat
|
||||
from .Scope import Scope
|
||||
from ..Env import Env
|
||||
|
||||
class OutputDirectiveFactory:
|
||||
"""Factory for creating output directives"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize factory"""
|
||||
pass
|
||||
|
||||
def get_output_directive(self, expr_list, location) -> 'Output':
|
||||
"""
|
||||
Get output directive
|
||||
|
||||
Args:
|
||||
expr_list: Expression list
|
||||
location: Location information
|
||||
|
||||
Returns:
|
||||
Output directive
|
||||
"""
|
||||
from .ast.Output import Output
|
||||
return Output(expr_list)
|
||||
|
||||
|
||||
# Create singleton instance
|
||||
me = OutputDirectiveFactory()
|
||||
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}"
|
||||
|
||||
|
||||
133
template/stat/Scope.py
Normal file
133
template/stat/Scope.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Scope - Template Execution Scope
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class Scope:
|
||||
"""Template execution scope"""
|
||||
|
||||
def __init__(self, data: Dict = None, shared_objects: Dict = None):
|
||||
"""
|
||||
Initialize scope
|
||||
|
||||
Args:
|
||||
data: Data dictionary
|
||||
shared_objects: Shared objects dictionary
|
||||
"""
|
||||
self._parent = None
|
||||
self._data = data or {}
|
||||
self._shared_objects = shared_objects or {}
|
||||
self._local_vars: Dict[str, Any] = {}
|
||||
|
||||
def set_parent(self, parent: 'Scope'):
|
||||
"""Set parent scope"""
|
||||
self._parent = parent
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
"""
|
||||
Get value by key, searching through scopes
|
||||
|
||||
Args:
|
||||
key: Variable key
|
||||
|
||||
Returns:
|
||||
Value or None
|
||||
"""
|
||||
# Check local vars first
|
||||
if key in self._local_vars:
|
||||
return self._local_vars[key]
|
||||
|
||||
# Check data
|
||||
if key in self._data:
|
||||
return self._data[key]
|
||||
|
||||
# Check shared objects
|
||||
if key in self._shared_objects:
|
||||
return self._shared_objects[key]
|
||||
|
||||
# Check parent scope
|
||||
if self._parent:
|
||||
return self._parent.get(key)
|
||||
|
||||
return None
|
||||
|
||||
def set(self, key: str, value: Any):
|
||||
"""
|
||||
Set value in local scope
|
||||
|
||||
Args:
|
||||
key: Variable key
|
||||
value: Value to set
|
||||
"""
|
||||
self._local_vars[key] = value
|
||||
|
||||
def set_local(self, key: str, value: Any):
|
||||
"""Set local variable"""
|
||||
self._local_vars[key] = value
|
||||
|
||||
def remove(self, key: str) -> Any:
|
||||
"""
|
||||
Remove and return value
|
||||
|
||||
Args:
|
||||
key: Variable key
|
||||
|
||||
Returns:
|
||||
Removed value or None
|
||||
"""
|
||||
# Try local vars
|
||||
if key in self._local_vars:
|
||||
return self._local_vars.pop(key)
|
||||
|
||||
# Try data
|
||||
if key in self._data:
|
||||
return self._data.pop(key, None)
|
||||
|
||||
return None
|
||||
|
||||
def contains(self, key: str) -> bool:
|
||||
"""Check if key exists in any scope"""
|
||||
return self.get(key) is not None
|
||||
|
||||
def keys(self) -> set:
|
||||
"""Get all keys in scope"""
|
||||
keys = set()
|
||||
keys.update(self._local_vars.keys())
|
||||
keys.update(self._data.keys())
|
||||
keys.update(self._shared_objects.keys())
|
||||
if self._parent:
|
||||
keys.update(self._parent.keys())
|
||||
return keys
|
||||
|
||||
def get_local_vars(self) -> Dict[str, Any]:
|
||||
"""Get local variables"""
|
||||
return self._local_vars.copy()
|
||||
|
||||
def get_data(self) -> Dict[str, Any]:
|
||||
"""Get data dictionary"""
|
||||
return self._data.copy()
|
||||
|
||||
def get_shared_objects(self) -> Dict[str, Any]:
|
||||
"""Get shared objects"""
|
||||
return self._shared_objects.copy()
|
||||
|
||||
def clear_local_vars(self):
|
||||
"""Clear local variables"""
|
||||
self._local_vars.clear()
|
||||
|
||||
def new_scope(self) -> 'Scope':
|
||||
"""
|
||||
Create a new scope with this scope as parent
|
||||
|
||||
Returns:
|
||||
New scope instance
|
||||
"""
|
||||
new_scope = Scope(self._data.copy(), self._shared_objects.copy())
|
||||
new_scope.set_parent(self)
|
||||
return new_scope
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Scope(local_vars={len(self._local_vars)}, data={len(self._data)}, shared={len(self._shared_objects)})"
|
||||
48
template/stat/ast/Define.py
Normal file
48
template/stat/ast/Define.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Define - Template Function Definition
|
||||
"""
|
||||
|
||||
from .Stat import Stat
|
||||
from ..Scope import Scope
|
||||
from ...Env import Env
|
||||
|
||||
class Define(Stat):
|
||||
"""Template function definition"""
|
||||
|
||||
def __init__(self, function_name: str):
|
||||
"""
|
||||
Initialize define statement
|
||||
|
||||
Args:
|
||||
function_name: Function name
|
||||
"""
|
||||
self._function_name = function_name
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> None:
|
||||
"""
|
||||
Execute define statement
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
"""
|
||||
# Register the function with the environment
|
||||
env.add_function(self._function_name, self)
|
||||
|
||||
def get_function_name(self) -> str:
|
||||
"""Get function name"""
|
||||
return self._function_name
|
||||
|
||||
def set_env_for_dev_mode(self, env: Env):
|
||||
"""Set environment for dev mode"""
|
||||
pass
|
||||
|
||||
def is_source_modified_for_dev_mode(self) -> bool:
|
||||
"""Check if source is modified (for dev mode)"""
|
||||
return False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Define({self._function_name})"
|
||||
106
template/stat/ast/For.py
Normal file
106
template/stat/ast/For.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal For - For Directive
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from .Stat import Stat, StatList
|
||||
from ..Scope import Scope
|
||||
from ...Env import Env
|
||||
|
||||
class For(Stat):
|
||||
"""For loop directive"""
|
||||
|
||||
def __init__(self, var_name: str, iter_expr: str, body: Stat):
|
||||
"""
|
||||
Initialize for directive
|
||||
|
||||
Args:
|
||||
var_name: Loop variable name
|
||||
iter_expr: Expression to iterate over
|
||||
body: Loop body
|
||||
"""
|
||||
self._var_name = var_name
|
||||
self._iter_expr = iter_expr
|
||||
self._body = body
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> None:
|
||||
"""
|
||||
Execute for loop
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
"""
|
||||
try:
|
||||
# Get the iterable from the expression
|
||||
iterable = eval(self._iter_expr, {}, scope._data.copy())
|
||||
|
||||
# Convert iterable to list for easier manipulation
|
||||
items = list(iterable)
|
||||
size = len(items)
|
||||
|
||||
# Create a ForLoopInfo class to hold loop information
|
||||
class ForLoopInfo:
|
||||
def __init__(self, size, index, outer=None):
|
||||
self._size = size
|
||||
self._index = index
|
||||
self._outer = outer
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return self._index
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return self._index + 1
|
||||
|
||||
@property
|
||||
def first(self):
|
||||
return self._index == 0
|
||||
|
||||
@property
|
||||
def last(self):
|
||||
return self._index == self._size - 1
|
||||
|
||||
@property
|
||||
def odd(self):
|
||||
return self._index % 2 == 0
|
||||
|
||||
@property
|
||||
def even(self):
|
||||
return self._index % 2 == 1
|
||||
|
||||
@property
|
||||
def outer(self):
|
||||
return self._outer
|
||||
|
||||
# Get outer for info if exists
|
||||
outer_for = scope._data.get("for")
|
||||
|
||||
# Iterate over the items
|
||||
for index, item in enumerate(items):
|
||||
# Create for loop info
|
||||
for_loop = ForLoopInfo(size, index, outer_for)
|
||||
|
||||
# Create a new scope for this iteration
|
||||
loop_scope = Scope(scope._data.copy(), scope._shared_objects.copy())
|
||||
loop_scope.set_parent(scope)
|
||||
loop_scope._data[self._var_name] = item
|
||||
loop_scope._data["for"] = for_loop
|
||||
|
||||
# Execute the loop body
|
||||
if self._body:
|
||||
self._body.exec(env, loop_scope, writer)
|
||||
except Exception as e:
|
||||
# Handle errors gracefully
|
||||
writer.write(f"Error in for loop: {e}")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"For(var={self._var_name}, expr={self._iter_expr})"
|
||||
40
template/stat/ast/Output.py
Normal file
40
template/stat/ast/Output.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Output - Output Directive
|
||||
"""
|
||||
|
||||
from .Stat import Stat
|
||||
from ..Scope import Scope
|
||||
from ...Env import Env
|
||||
from ...expr.ast import ExprList
|
||||
|
||||
class Output(Stat):
|
||||
"""Output directive for template expressions"""
|
||||
|
||||
def __init__(self, expr_list: ExprList):
|
||||
"""
|
||||
Initialize output directive
|
||||
|
||||
Args:
|
||||
expr_list: Expression list to evaluate
|
||||
"""
|
||||
self._expr_list = expr_list
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> None:
|
||||
"""
|
||||
Execute output directive
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
"""
|
||||
if self._expr_list:
|
||||
result = self._expr_list.eval(scope)
|
||||
if result is not None:
|
||||
if hasattr(writer, 'write'):
|
||||
writer.write(str(result))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Output({self._expr_list})"
|
||||
79
template/stat/ast/Stat.py
Normal file
79
template/stat/ast/Stat.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Stat - Abstract Syntax Tree Base Class
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
from ..Scope import Scope
|
||||
from ...Env import Env
|
||||
|
||||
class Stat:
|
||||
"""Base class for statement AST nodes"""
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> Any:
|
||||
"""
|
||||
Execute the statement
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
|
||||
Returns:
|
||||
Execution result
|
||||
"""
|
||||
raise NotImplementedError("Stat.exec() must be implemented by subclasses")
|
||||
|
||||
def get_stat_list(self) -> 'StatList':
|
||||
"""Get as StatList if applicable"""
|
||||
return None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Stat({self.__class__.__name__})"
|
||||
|
||||
|
||||
class StatList(Stat):
|
||||
"""Statement list containing multiple statements"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize statement list"""
|
||||
self._stats: list = []
|
||||
|
||||
def add_stat(self, stat: Stat):
|
||||
"""
|
||||
Add statement to list
|
||||
|
||||
Args:
|
||||
stat: Statement to add
|
||||
"""
|
||||
if stat:
|
||||
self._stats.append(stat)
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> Any:
|
||||
"""
|
||||
Execute all statements in list
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
|
||||
Returns:
|
||||
Last statement result or None
|
||||
"""
|
||||
result = None
|
||||
for stat in self._stats:
|
||||
result = stat.exec(env, scope, writer)
|
||||
return result
|
||||
|
||||
def get_stats(self) -> list:
|
||||
"""Get all statements"""
|
||||
return self._stats.copy()
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get number of statements"""
|
||||
return len(self._stats)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"StatList({len(self._stats)} statements)"
|
||||
81
template/stat/ast/Text.py
Normal file
81
template/stat/ast/Text.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
JFinal Text - Text Statement
|
||||
"""
|
||||
|
||||
from .Stat import Stat
|
||||
from ..Scope import Scope
|
||||
from ...Env import Env
|
||||
|
||||
class Text(Stat):
|
||||
"""Text statement for template content"""
|
||||
|
||||
def __init__(self, content: str):
|
||||
"""
|
||||
Initialize text statement
|
||||
|
||||
Args:
|
||||
content: Text content
|
||||
"""
|
||||
self._content = content
|
||||
|
||||
def exec(self, env: Env, scope: Scope, writer) -> None:
|
||||
"""
|
||||
Execute text statement
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
writer: Output writer
|
||||
"""
|
||||
if hasattr(writer, 'write'):
|
||||
# Parse and evaluate template expressions
|
||||
parsed_content = self._parse_expressions(env, scope)
|
||||
writer.write(parsed_content)
|
||||
|
||||
def _parse_expressions(self, env: Env, scope: Scope) -> str:
|
||||
"""
|
||||
Parse and evaluate template expressions in content
|
||||
|
||||
Args:
|
||||
env: Template environment
|
||||
scope: Execution scope
|
||||
|
||||
Returns:
|
||||
Parsed and evaluated content
|
||||
"""
|
||||
import re
|
||||
|
||||
def replace_expression(match):
|
||||
"""Replace expression with evaluated value"""
|
||||
expr_str = match.group(1).strip()
|
||||
|
||||
# Try to evaluate expression
|
||||
try:
|
||||
# First try variable access
|
||||
if expr_str.isidentifier():
|
||||
value = scope.get(expr_str)
|
||||
return str(value) if value is not None else ''
|
||||
|
||||
# Try arithmetic expression
|
||||
try:
|
||||
# Simple arithmetic evaluation
|
||||
# This is a simplified implementation
|
||||
result = eval(expr_str, {})
|
||||
return str(result)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Fallback to original expression
|
||||
return match.group(0)
|
||||
|
||||
except Exception as e:
|
||||
return f"{match.group(0)} [Error: {e}]"
|
||||
|
||||
# Replace all #(...) expressions
|
||||
pattern = r'#\((.*?)\)'
|
||||
return re.sub(pattern, replace_expression, self._content)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Text('{self._content[:50]}...')"
|
||||
12
template/stat/ast/__init__.py
Normal file
12
template/stat/ast/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Export statement AST classes
|
||||
from .Stat import Stat, StatList
|
||||
from .Text import Text
|
||||
from .Define import Define
|
||||
from .Output import Output
|
||||
|
||||
__all__ = [
|
||||
'Stat', 'StatList',
|
||||
'Text',
|
||||
'Define',
|
||||
'Output'
|
||||
]
|
||||
Reference in New Issue
Block a user