调整版本并做测试

This commit is contained in:
2026-02-27 17:10:54 +08:00
parent fa673138f6
commit 31be9d0e97
77 changed files with 679 additions and 25 deletions

View 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)})"

View 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()

View File

@@ -0,0 +1,383 @@
#!/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.')
# Remove newline characters from expression
expr_str = expr_str.replace('\n', '').replace('\r', '')
# Split expression by | to handle filters
expr_parts = expr_str.split('|')
main_expr = expr_parts[0].strip()
# Evaluate main expression first
if '??' in main_expr:
# Custom evaluation for expressions with ?? operator
def evaluate_null_coalescing(expr):
if '??' not in expr:
try:
return eval(expr, {}, wrapped_vars)
except (NameError, AttributeError):
return None
# Process from right to left for nested ??
idx = expr.rfind('??')
# Find left expression
left_expr = expr[:idx].strip()
right_expr = expr[idx+2:].strip()
# Evaluate left part
left_val = evaluate_null_coalescing(left_expr)
if left_val is not None:
return left_val
else:
# Evaluate right part
return evaluate_null_coalescing(right_expr)
result = evaluate_null_coalescing(main_expr)
else:
# Regular evaluation for expressions without ??
try:
result = eval(main_expr, {}, wrapped_vars)
except (NameError, AttributeError):
result = None
# Apply filters
for filter_part in expr_parts[1:]:
filter_part = filter_part.strip()
if not filter_part:
continue
# Parse filter name and arguments
if '(' in filter_part:
filter_name = filter_part[:filter_part.find('(')].strip()
# Extract arguments
args_str = filter_part[filter_part.find('(')+1:filter_part.rfind(')')].strip()
# Simple argument parsing - split by commas, ignoring commas inside quotes
args = []
current_arg = ''
in_quotes = False
quote_char = ''
for char in args_str:
if char in "'\"" and (not in_quotes or char == quote_char):
if not in_quotes:
in_quotes = True
quote_char = char
else:
in_quotes = False
current_arg += char
elif char == ',' and not in_quotes:
args.append(current_arg.strip())
current_arg = ''
else:
current_arg += char
if current_arg:
args.append(current_arg.strip())
else:
filter_name = filter_part.strip()
args = []
# Apply filter
if filter_name == 'join':
# Join filter implementation
if len(args) > 0:
# Remove quotes from separator if present
sep = args[0]
if (sep.startswith('"') and sep.endswith('"')) or (sep.startswith("'") and sep.endswith("'")):
sep = sep[1:-1]
else:
sep = ''
if isinstance(result, (list, tuple)):
result = sep.join(str(item) for item in result)
else:
result = str(result)
elif filter_name == 'upper':
# Upper case filter
result = str(result).upper()
elif filter_name == 'lower':
# Lower case filter
result = str(result).lower()
elif filter_name == 'strip':
# Strip whitespace filter
result = str(result).strip()
return result
except Exception as e:
# Handle evaluation errors gracefully
return f"Error evaluating expression '{self._expr_str}': {e}"

View 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)})"

View 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})"

View File

@@ -0,0 +1,130 @@
#!/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:
# Wrap dict values to allow 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
# Create wrapped vars similar to SimpleExprList.eval()
wrapped_vars = {}
for key, value in scope._data.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
# Get the iterable from the expression
iterable = eval(self._iter_expr, {}, wrapped_vars)
# 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})"

View 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})"

View 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)"

View 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]}...')"

View 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'
]