首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

設計可擴展應用程式(四):以 S-Expression 快速構建配置語言實戰

     編輯:Java 相關知識

前文指出:若掌握識別配置腳本、選擇允許配置、識別需黑箱擴展環境、衡量擴展複雜度,底層代碼可用性非問題。提供擴展性實際上是在構建一種語言。S-Expression(S運算式)是快速建立配置語言的有效手段。本文深入研究並示範如何用 S-Expression 為特定應用快速建立配置語言。

S-Expression 基礎與優勢

S-Expression 以圓括號分隔元素列表,三種形式:空列表、非空列表、單一原子元素。優勢:

  • 易於解析:通用解析器讀入數據,程式再檢查具體語法約束,獲得早期錯誤檢測、安全性提升,省去編寫維護常規解析器的開銷。
  • 錯誤訊息精確有用:優於解析器生成器產出的解析器。
  • 輕量、建立快速:較 XML 更精簡,腳本更易讀寫。

實戰範例:為編輯器添加巨集支援

允許用戶定義基本操作的複雜序列,支援迴圈/遞歸結構。

(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 關鍵字)。撰寫範例腳本是構建語言的良好開端,亦可作為解釋器單元測試。

確定腳本語言:BNF 語法定義


<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 分割階段(本質為括號匹配),大幅簡化流程:

  1. 標記化:StreamTokenizer 產生標記流(左/右括號、詞)。
  2. S-Expression 解析:以堆疊處理流,遞歸構建 S-Expression 樹(SList、Atom)。
  3. 語法樹解析:為每個語法結構定義類別(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 底層構造實現。

結語

四部分系列完結。技術如雙面刃:能有效復用代碼,濫用則導致複雜度失控。謹慎使用!

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved