前文指出:若掌握識別配置腳本、選擇允許配置、識別需黑箱擴展環境、衡量擴展複雜度,底層代碼可用性非問題。提供擴展性實際上是在構建一種語言。S-Expression(S運算式)是快速建立配置語言的有效手段。本文深入研究並示範如何用 S-Expression 為特定應用快速建立配置語言。
S-Expression 以圓括號分隔元素列表,三種形式:空列表、非空列表、單一原子元素。優勢:
允許用戶定義基本操作的複雜序列,支援迴圈/遞歸結構。
(define (cutAndPasteAtEnd)
(sequence
(cut HIGHLIGHTED_TEXT)
(move-to END_OF_DOCUMENT)
(paste CLIPBOARD)))
(define (find-and-replace target replacement)
(move-to BEGINNING_OF_DOCUMENT)
(while (not AT_END)
(cut NEXT_WORD)
(if (equals CLIPBOARD target)
(paste replacement)
(paste target))
(move-to NEXT_WORD)))
前綴標記法易解析,if 三部分依序為條件、滿足、不滿足分支(無 else 關鍵字)。撰寫範例腳本是構建語言的良好開端,亦可作為解釋器單元測試。
<script> ::= (<definitions> <expression>)
<definitions> ::= <empty> | <definition> <definitions>
<definition> ::= (define (<var> <args>) <statement>)
<args> ::= <empty> | <var> args
<statement> ::= (cut <name>) | (paste <name>) | (move-to <name>)
| (sequence <statements>) | (if <expression> <statement> <statement>)
| (while <expression> <statement> <statement>) | (return <expression>)
| (<var> <args>)
...(完整 BNF 見原文)
語言含過程定義,支援抽象重用。省略 let、賦值、靜態型別系統(腳本語言常省略以減少冗長與過度拒絕合法程式)。
較常規二階段(標記化、解析),增加S-Expression 分割階段(本質為括號匹配),大幅簡化流程:
StreamTokenizer 產生標記流(左/右括號、詞)。SList、Atom)。Script、CutStatement、IfStatement...),遞歸解析 S-Expression 為語法樹,採 Visitor 模式分離階段邏輯。// S-Expression 解析核心遞歸
public static SExp parseNextSExp(StackI tokens) {
Object next = tokens.pop();
if (next.equals(LEFT_PAREN)) {
SList result = new SEmpty();
while (!tokens.empty()) {
next = tokens.peek();
if (next.equals(RIGHT_PAREN)) { tokens.pop(); return result.reverse(); }
else { result = new SCons(parseNextSExp(tokens), result); }
}
throw new SExpParseException("Unmatched left parenthesis.");
} else if (next.equals(RIGHT_PAREN)) { throw ...; }
else { return new Atom(next); }
}
解析後可輕易實作型別檢查、約束檢查(如類別層次無循環)、求值。採 Visitor 模式每階段獨立:
class Evaluator extends SyntaxTreeVisitor {
Application app;
public Object forCutStatement(CutStatement that) {
app.cut(that.getName()); return new VoidObject();
}
public Object forWhileStatement(WhileStatement that) {
while (that.getTest().accept(this).equals(Boolean.TRUE))
that.getBody().accept(this);
return new VoidObject();
}
public Object forIfStatement(IfStatement that) {
if (that.getTest().accept(this).equals(Boolean.TRUE))
that.getConsequent().accept(this);
else that.getAlternative().accept(this);
return new VoidObject();
}
}
基本操作求值對應應用程式方法調用,複雜結構仰賴 Java 底層構造實現。
四部分系列完結。技術如雙面刃:能有效復用代碼,濫用則導致複雜度失控。謹慎使用!