#!/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}"