mirror of
https://github.com/KenanZhu/AutoLibrary.git
synced 2026-06-18 07:23:03 +08:00
refactor(autoscript): 使用观察者模式解耦解析与预检查/编排流程
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (c) 2026 KenanZhu.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
This software is provided "as is", without any warranty of any kind.
|
||||||
|
You may use, modify, and distribute this file under the terms of the MIT License.
|
||||||
|
See the LICENSE file for details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingObserver:
|
||||||
|
"""
|
||||||
|
Base observer for AutoScript parsing events.
|
||||||
|
|
||||||
|
Subclass and override the relevant methods to react to
|
||||||
|
tokenization / parsing events produced by ASTokenizer.
|
||||||
|
This is the core abstraction that lets pre-check and
|
||||||
|
orchestration modules subscribe to the same parsing pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def onParseStart(
|
||||||
|
self,
|
||||||
|
script_text: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Called when tokenization of a new script begins.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
script_text (str): The full script text being parsed.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onTokenParsed(
|
||||||
|
self,
|
||||||
|
kind: str | None,
|
||||||
|
data,
|
||||||
|
line_num: int,
|
||||||
|
raw_line: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Called after each script line has been classified as a token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kind (str | None): Token kind (K_IF, K_ELSE_IF, K_ELSE,
|
||||||
|
K_ENDIF, K_SET, K_ADD, K_SUB) or
|
||||||
|
None if unrecognised.
|
||||||
|
data: Token payload — condition string for IF/ELSE IF,
|
||||||
|
(target, value) tuple for SET/ADD/SUB,
|
||||||
|
None for ELSE/ENDIF/unrecognised.
|
||||||
|
line_num (int): 1-based line number.
|
||||||
|
raw_line (str): The stripped raw line text.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onParseComplete(
|
||||||
|
self,
|
||||||
|
statements: list
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Called when flat tokenization is complete.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
statements (list[Stmt]): The list of parsed Stmt objects.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onASTReady(
|
||||||
|
self,
|
||||||
|
ast
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Called when full AST construction is complete (after parse()).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ast (Script): The root Script AST node.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
+109
-26
@@ -326,12 +326,27 @@ class ASTokenizer:
|
|||||||
"""
|
"""
|
||||||
Tokenizer / parser for the AutoScript DSL.
|
Tokenizer / parser for the AutoScript DSL.
|
||||||
|
|
||||||
Provides three entry points:
|
Main class-level entry points (engine-facing):
|
||||||
- classifyLine(line) — single-line classifier.
|
- classifyLine(line) — single-line classifier.
|
||||||
- tokenize(script) — flat Stmt list.
|
- tokenize(script) — flat Stmt list.
|
||||||
- parse(script) — structured AST (Script root).
|
- parse(script) — structured AST (Script root).
|
||||||
|
|
||||||
|
Observer-enabled API (used by pre-check & orchestration):
|
||||||
|
>>> obs = ScriptPrecheckObserver()
|
||||||
|
>>> stmts = ASTokenizer.tokenizeWithObservers(script, [obs])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _notifyObservers(
|
||||||
|
cls,
|
||||||
|
observers: list,
|
||||||
|
method: str,
|
||||||
|
*args
|
||||||
|
):
|
||||||
|
|
||||||
|
for obs in observers:
|
||||||
|
getattr(obs, method)(*args)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _matchLine(
|
def _matchLine(
|
||||||
cls,
|
cls,
|
||||||
@@ -345,18 +360,29 @@ class ASTokenizer:
|
|||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def classifyLine(
|
def _buildStmt(
|
||||||
cls,
|
cls,
|
||||||
stripped: str
|
stripped: str,
|
||||||
):
|
kind: str | None,
|
||||||
|
data
|
||||||
|
) -> Stmt:
|
||||||
|
|
||||||
kind, data = cls._matchLine(stripped)
|
stmt = Stmt(kind=kind, raw_line=stripped)
|
||||||
if kind is None or kind == K_PASS:
|
if kind == K_IF or kind == K_ELSE_IF:
|
||||||
return None
|
stmt.condition = data
|
||||||
return (kind, data)
|
elif kind == K_SET:
|
||||||
|
stmt.target, stmt.value = data
|
||||||
|
stmt.op_type = OP_SET
|
||||||
|
elif kind == K_ADD:
|
||||||
|
stmt.target, stmt.value = data
|
||||||
|
stmt.op_type = OP_ADD
|
||||||
|
elif kind == K_SUB:
|
||||||
|
stmt.target, stmt.value = data
|
||||||
|
stmt.op_type = OP_SUB
|
||||||
|
return stmt
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tokenize(
|
def _tokenizeImpl(
|
||||||
cls,
|
cls,
|
||||||
script: str
|
script: str
|
||||||
) -> list:
|
) -> list:
|
||||||
@@ -367,29 +393,15 @@ class ASTokenizer:
|
|||||||
if not stripped:
|
if not stripped:
|
||||||
continue
|
continue
|
||||||
kind, data = cls._matchLine(stripped)
|
kind, data = cls._matchLine(stripped)
|
||||||
stmt = Stmt(kind=kind, raw_line=stripped)
|
statements.append(cls._buildStmt(stripped, kind, data))
|
||||||
|
|
||||||
if kind == K_IF or kind == K_ELSE_IF:
|
|
||||||
stmt.condition = data
|
|
||||||
elif kind == K_SET:
|
|
||||||
stmt.target, stmt.value = data
|
|
||||||
stmt.op_type = OP_SET
|
|
||||||
elif kind == K_ADD:
|
|
||||||
stmt.target, stmt.value = data
|
|
||||||
stmt.op_type = OP_ADD
|
|
||||||
elif kind == K_SUB:
|
|
||||||
stmt.target, stmt.value = data
|
|
||||||
stmt.op_type = OP_SUB
|
|
||||||
statements.append(stmt)
|
|
||||||
return statements
|
return statements
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(
|
def _parseTokens(
|
||||||
cls,
|
cls,
|
||||||
script: str
|
tokens: list
|
||||||
) -> Script:
|
) -> Script:
|
||||||
|
|
||||||
tokens = cls.tokenize(script)
|
|
||||||
body = []
|
body = []
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(tokens):
|
while i < len(tokens):
|
||||||
@@ -420,6 +432,77 @@ class ASTokenizer:
|
|||||||
i += 1
|
i += 1
|
||||||
return Script(body=body)
|
return Script(body=body)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def classifyLine(
|
||||||
|
cls,
|
||||||
|
stripped: str
|
||||||
|
):
|
||||||
|
|
||||||
|
kind, data = cls._matchLine(stripped)
|
||||||
|
if kind is None or kind == K_PASS:
|
||||||
|
return None
|
||||||
|
return (kind, data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tokenize(
|
||||||
|
cls,
|
||||||
|
script: str
|
||||||
|
) -> list:
|
||||||
|
|
||||||
|
return cls._tokenizeImpl(script)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(
|
||||||
|
cls,
|
||||||
|
script: str
|
||||||
|
) -> Script:
|
||||||
|
|
||||||
|
return cls._parseTokens(cls._tokenizeImpl(script))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tokenizeWithObservers(
|
||||||
|
cls,
|
||||||
|
script: str,
|
||||||
|
observers: list
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Tokenize and notify observers for each classified line.
|
||||||
|
|
||||||
|
Fires onParseStart, onTokenParsed, and onParseComplete
|
||||||
|
events to each observer. This is the single tokenization
|
||||||
|
pipeline shared by pre-check and orchestration modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cls._notifyObservers(observers, "onParseStart", script)
|
||||||
|
statements = []
|
||||||
|
for i, raw_line in enumerate(script.split("\n"), 1):
|
||||||
|
stripped = raw_line.strip()
|
||||||
|
if not stripped:
|
||||||
|
continue
|
||||||
|
kind, data = cls._matchLine(stripped)
|
||||||
|
cls._notifyObservers(observers, "onTokenParsed", kind, data, i, stripped)
|
||||||
|
statements.append(cls._buildStmt(stripped, kind, data))
|
||||||
|
cls._notifyObservers(observers, "onParseComplete", statements)
|
||||||
|
return statements
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parseWithObservers(
|
||||||
|
cls,
|
||||||
|
script: str,
|
||||||
|
observers: list
|
||||||
|
) -> Script:
|
||||||
|
"""
|
||||||
|
Parse and notify observers throughout the pipeline.
|
||||||
|
|
||||||
|
Calls tokenizeWithObservers (which fires per-token events),
|
||||||
|
then builds the AST and fires onASTReady.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tokens = cls.tokenizeWithObservers(script, observers)
|
||||||
|
ast = cls._parseTokens(tokens)
|
||||||
|
cls._notifyObservers(observers, "onASTReady", ast)
|
||||||
|
return ast
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parseIfBlock(
|
def _parseIfBlock(
|
||||||
cls,
|
cls,
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
AutoScript module for the AutoLibrary project.
|
AutoScript module for the AutoLibrary project.
|
||||||
|
A lightweight scripting DSL for preprocessing user reservation data.
|
||||||
A lightweight scripting DSL for preprocessing user reservation data
|
|
||||||
in repeatable timer tasks. Supports IF/ELSE IF/ELSE/END IF control
|
|
||||||
flow, SET assignments, .ADD./.SUB. operations, and rich comparisons.
|
|
||||||
|
|
||||||
Public API:
|
|
||||||
- execute(script_text, target_data): Execute an AutoScript.
|
|
||||||
- addTargetVar(name, var_type, key_path, display_name): Register a variable.
|
|
||||||
- registerDefaultTargetVars(): Register all built-in target variables.
|
|
||||||
- META_VARS: dict of built-in read-only meta variables.
|
|
||||||
- ALL_VARIABLES: dict of all available variables (display_name -> (name, type)).
|
|
||||||
- ASTokenizer: Unified tokenizer for the orchestration dialog and engine.
|
|
||||||
"""
|
"""
|
||||||
from autoscript.ASTokenizer import (
|
from autoscript.ASTokenizer import (
|
||||||
ASTokenizer,
|
ASTokenizer,
|
||||||
@@ -27,6 +16,7 @@ from autoscript.ASEngine import (
|
|||||||
addTargetVar,
|
addTargetVar,
|
||||||
)
|
)
|
||||||
from autoscript.ASObject import _META_VARS as META_VARS
|
from autoscript.ASObject import _META_VARS as META_VARS
|
||||||
|
from autoscript.ASObserver import ParsingObserver
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"execute",
|
"execute",
|
||||||
@@ -41,7 +31,8 @@ __all__ = [
|
|||||||
"IfNode",
|
"IfNode",
|
||||||
"SetNode",
|
"SetNode",
|
||||||
"OpNode",
|
"OpNode",
|
||||||
"ElifNode"
|
"ElifNode",
|
||||||
|
"ParsingObserver",
|
||||||
]
|
]
|
||||||
|
|
||||||
# All variables available to scripts (display_name -> (name, type)).
|
# All variables available to scripts (display_name -> (name, type)).
|
||||||
|
|||||||
Reference in New Issue
Block a user