国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[Java] 嘗試詞法分析器的總結(jié)

flybywind / 2479人閱讀

摘要:寫在前面由于我對(duì)寫解析器只有閱讀了幾篇文章的知識(shí)量,因此水平并不是很高,此文權(quán)當(dāng)一次個(gè)人總結(jié),無法保證所涉及的知識(shí)點(diǎn)思路完全無誤,如有錯(cuò)誤,還請(qǐng)各位大佬指正。除此之外的狀態(tài)都是不合法的,這也就是有時(shí)候解析類的包比如會(huì)看到的錯(cuò)誤的情況。

寫在前面

由于我對(duì)寫解析器只有 閱讀了幾篇文章 的知識(shí)量,因此水平并不是很高,此文權(quán)當(dāng)一次個(gè)人總結(jié),無法保證所涉及的知識(shí)點(diǎn)、思路完全無誤,如有錯(cuò)誤,還請(qǐng)各位大佬指正。

從一個(gè)正整數(shù)表達(dá)式開始

這篇文章圍繞的僅僅是一個(gè) 正整數(shù)表達(dá)式,而且它很簡(jiǎn)單,不會(huì)出現(xiàn)括號(hào)嵌套等情況,我們的目標(biāo)只是把

10 * 5 + 1

解析為一個(gè) Token 序列,如下:

[
    { type: NUMBER,   value: `10` },
    { type: OPERATOR, value: `*`  },
    { type: NUMBER,   value: `5`  },
    { type: OPERATOR, value: `+`  },
    { type: NUMBER,   value: `1`  }
]

我習(xí)慣從簡(jiǎn)單的開始,那么我們先從一個(gè)最簡(jiǎn)單的、只有個(gè)位數(shù)、沒有空格的式子開始:

1+1
最簡(jiǎn)單的思路

其實(shí)詞法分析器要做的事本質(zhì)上很簡(jiǎn)單:對(duì)輸入的字符串進(jìn)行遍歷,分割成有意義的 Token
因此,最簡(jiǎn)單的思路就是一個(gè) for 循環(huán):

String expression = "1+1";
for (char ch : expression.toCharArray()) {
    // 在這里進(jìn)行處理
}

所以我們定義一個(gè) Scanner,為了后續(xù)方便,順手實(shí)現(xiàn)個(gè)簡(jiǎn)單的單例吧:

public class Scanner {
  
      private static volatile Scanner instance;

    public static Scanner getInstance() {
          if (Scanner.instance == null) {
              synchronized ( Scanner.class ) {
                  if (Scanner.instance == null) {
                    Scanner.instance = new Scanner();
                }
            }
        }
        return Scanner.instance;
    }

    private String expression;

    public Scanner from(String expression) {
        this.expression = expression;
        return this;
    }

      public void process() {
        for (char ch : expression.toCharArray()) {
            // 在這里進(jìn)行處理
        }
    }
  
     public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("1+1");
          scanner.process();
    }
}
定義 Token 類型

在當(dāng)前的 1+1 表達(dá)式中,涉及到的 Token 不多,只有數(shù)字、操作符,因此用一個(gè)枚舉類即可表述:

public enum Type {
    INIT,
    NUMBER,
    OPERATOR,
    UNKNOWN;
    
    public static Type of(char ch) {
        if ("0" <= ch && ch <= "9") {
            return NUMBER;
        }
        if ("+-*/".indexOf(ch) != -1) {
            return OPERATOR;
        }
        return UNKNOWN;
    }
}

同時(shí)該枚舉類承擔(dān)辨識(shí)字符類型的工作:

Type.of("1") // NUMBER
Type.of("+") // OPERATOR
Type.of("a") // UNKNOWN
定義 Token
public class Token {
    // 一個(gè) Token 的類型一旦確定,就不可能再改變。
    private final Type type;
    // 用以存儲(chǔ) Token 的值。
    private final StringBuffer value;
    
    public Token(Type type) {
        this.type = type;
        this.value = new StringBuffer();
    }
    
    public void appendValue(char ch) {
        this.value.append(ch);
    }
    
    public String getValue() {
        return this.value.toString();
    }
    
    public Type getType() {
        return this.type;
    }
    
    @Override
    public String toString() {
        return String.format("{type: %s, value: `%s`}",
            this.getType().name(),
            this.getValue());
    }
}
處理 1+1
public class Scanner {
      // 省略...  
      public void process() {
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch));
            }
            Token token = new Token(type);
            token.appendValue(ch);
            System.out.println(token);
        }
    }
  
     public static void main(String ... args) {
        Scanner scanner = new Scanner("1+1");
          scanner.process();
    }
}

/** 輸出
 * {type: NUMBER,   value: `1`}
 * {type: OPERATOR, value: `+`}
 * {type: NUMBER,   value: `1`}
 */
現(xiàn)在來加點(diǎn)難度: 10+1

現(xiàn)在一個(gè)數(shù)字可能不止一位了,那么我們?cè)撛趺崔k呢?

使用狀態(tài)圖:

   ┌-[ 0-9 ]-┐    ┌-[ +|-|*|/ ]-┐   ┌-[ 0-9 ]-┐
---(  NUMBER  )--- (  OPERATOR  )---(  NUMBER  )---

具體的理論這里就不贅述了,有興趣可以自行查閱相關(guān)資料,這里簡(jiǎn)單說一下怎么用:

現(xiàn)在我們來列個(gè)表,看一下對(duì)于 10+1,在狀態(tài)上有什么變化:

字符 狀態(tài) Token
NULL INIT NULL
1 NUMBER {id: 0, type: NUMBER, value: 1}
0 NUMBER {id: 0, type: NUMBER, value: 10}
+ OPERATOR {id: 1, type: OPERATOR, value: +}
1 NUMBER {id: 2, type: NUMBER, value: 1}

可以看到,在讀到字符 10 時(shí),狀態(tài)沒有發(fā)生變化,也就是說它們是一個(gè)整體(或是一個(gè)整體的一部分)。
如果在 0 后面還有其他數(shù)字,那么直到引起狀態(tài)改變的字符出現(xiàn)之前,這些字符就組成了整個(gè) Token

同時(shí),我們還發(fā)現(xiàn)引入狀態(tài)圖后,有個(gè)有意思的事:

從 初始狀態(tài) INIT 開始,我們只允許后邊是 NUMBER 類型;
NUMBER 后邊允許 NUMBEROPERATOR 類型;
OPERATOR 后邊允許 NUMBER 類型。

除此之外的狀態(tài)都是不合法的,這也就是有時(shí)候解析類的包(比如 fast-json)會(huì)看到的 Invalid Character 錯(cuò)誤的情況。

所以我們需要改改代碼,同時(shí)為 Scanner 添加一個(gè)更新狀態(tài)的方法:

public class Scanner {
    // 省略
      private Token token;
  
      public void setToken(Token token) {
        this.token = token;
    }
      
      public void process() {
          // 初始化
          this.setToken(new Token(Type.INIT));
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch));
            }
    
            // 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支
            switch (token.getType()) {
                case INIT:
                    switch (type) {
                        // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。
                        case NUMBER:
                            this.setToken(new Token(Type.NUMBER));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
                case NUMBER:
                    switch (type) {
                        // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。
                        case NUMBER:
                            this.token.appendValue(ch);
                            break;
                        // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。
                        case OPERATOR:
                            this.setToken(new Token(Type.OPERATOR));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
                case OPERATOR:
                    switch (type) {
                        // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。
                        case NUMBER:
                            this.setToken(new Token(Type.NUMBER));
                            this.token.appendValue(ch);
                            break;
                        default:
                            throw new RuntimeException(String
                                                   .format("Invalid Character: `%c`", ch));
                    }
                    break;
            }
            System.out.println(token);
        }
    }
}
/** 輸出
 * {type: NUMBER,   value: `1`}
 * {type: NUMBER,   value: `10`}
 * {type: OPERATOR, value: `+`}
 * {type: NUMBER,   value: `1`}
 */
試著簡(jiǎn)化一下

我們剛才用了一個(gè)巨大無比的 switch 結(jié)構(gòu)來描述狀態(tài)圖,現(xiàn)在我們由內(nèi)而外試著簡(jiǎn)化這個(gè)巨無霸。

 先從內(nèi)部開始:
switch (type) {
    // 當(dāng)前是 ** 狀態(tài),遇到了 ** , 執(zhí)行 ** 操作。
    case NUMBER:
        // ...
        break;
    case ...
    default:
        throw new RuntimeException(String.format("Invalid Character: `%c`", ch));
}

其實(shí)稍微歸納總結(jié)一下就能發(fā)現(xiàn), 執(zhí)行 ** 操作 這部分,總的來說只有兩種:

NewToken
    對(duì)應(yīng)著
        {
            token = new Token(Type.NUMBER);
            token.appendValue(ch);
        }
AppendValue
    對(duì)應(yīng)著
        {
            token.appendValue(ch);
        }

現(xiàn)在我們?cè)僖胍粋€(gè)工具來幫助我們簡(jiǎn)化:表驅(qū)動(dòng)。

其實(shí)從上面的對(duì)應(yīng)關(guān)系不難發(fā)現(xiàn),我們可以用 HashMap 來簡(jiǎn)單模擬一個(gè)表,幫助我們減少工作。

在此之前,我們需要把上述關(guān)系中的 {操作} 部分用一個(gè)接口來解耦:

public interface Behavior {
    void apply(Token token, Type type, char ch);
}

然后我們來定義一個(gè)枚舉類 Behaviors 來表示操作類型:

public enum Behaviors {
    NewToken,
      AppendValue;

      private static final Scanner scanner;
    private static final HashMap behaviorMap;

    static {
          scanner = Scanner.getInstance();
        behaviorMap = new HashMap<>();
        behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
            token = new Token(type);
            token.appendValue(ch);
              scanner.setToken(token);
        });
        behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> {
            token.appendValue(ch);
        });
    }

    public void apply(Token token, Type type, char ch) {
        behaviorMap.get(this)
                .apply(token, type, ch);
    }
}

那么現(xiàn)在 執(zhí)行 操作 這部分,現(xiàn)在可以用 HashMap 來表述了:

// 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支
switch (token.getType()) {
    case INIT:
        HashMap behaviorsMap = new HashMap<>();
        // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。
        behaviorsMap.put(Type.NUMBER, Behaviors.NewToken);
        break;
      case NUMBER:
           HashMap behaviorsMap = new HashMap<>();
        // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。
        behaviorsMap.put(Type.NUMBER, Behaviors.AppendValue);
        // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。
        behaviorsMap.put(Type.Operator, Behaviors.NewToken);
         break;
    case OPERATOR:
        HashMap behaviorsMap = new HashMap<>();
        // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。
        behaviorsMap.put(Type.NUMBER, Behaviors.NewToken);
        break;
}

既然是 Java ,那么讓我們來讓這部分看起來 OO 一些:

public class BehaviorMap {
    private final HashMap map;
      
      public BehaviorMap() {
        this.map = new HashMap();
    }
      
      public BehaviorMap at(Type type, Behaviors behaviors) {
        this.map.put(type, behaviors);
          return this;
    }
  
     public BehaviorsTable done() {
          return BehaviorsTable.getInstance();
    }
}

現(xiàn)在再來看看:

// 根據(jù)當(dāng)前 Token 的類型,選擇不同的判斷分支
switch (token.getType()) {
    case INIT:
        BehaviorMap map = new BehaviorMap();
        map
               // 當(dāng)前是初始狀態(tài),遇到了數(shù)字, 切換狀態(tài)。
              .at(Type.NUMBER, Behaviors.NewToken);
        break;
      case NUMBER:
           BehaviorMap map = new BehaviorMap();
        map
              // 當(dāng)前是數(shù)字狀態(tài),遇到了數(shù)字,追加字符。
             .at(Type.NUMBER, Behaviors.AppendValue);
            // 當(dāng)前是數(shù)字狀態(tài),遇到了操作符,切換狀態(tài)。
            .at(Type.Operator, Behaviors.NewToken);
         break;
    case OPERATOR:
        BehaviorMap map = new BehaviorMap();
        map
              // 當(dāng)前是操作符狀態(tài),遇到了數(shù)字,切換狀態(tài)。
              .at(Type.NUMBER, Behaviors.NewToken);
        break;
}
簡(jiǎn)化外部

現(xiàn)在我們可以看到表驅(qū)動(dòng)對(duì)于消除判斷分支的威力了,那么我們可以用同樣的方法將外部 switch 也消除掉:

public class BehaviorsTable {

    private static volatile BehaviorsTable instance;

    public static BehaviorsTable getInstance() {
        if (BehaviorsTable.instance == null) {
            synchronized ( BehaviorsTable.class ) {
                if (BehaviorsTable.instance == null) {
                    BehaviorsTable.instance = new BehaviorsTable();
                }
            }
        }
        return BehaviorsTable.instance;
    }

    private final HashMap map;

    public BehaviorsTable () {
        this.map = new HashMap<>();
    }

    public BehaviorMap register(Type type) {
        BehaviorMap behaviorMap = new BehaviorMap();
        this.map.put(type, behaviorMap);
        return behaviorMap;
    }
}

現(xiàn)在整個(gè)巨大的 switch 結(jié)構(gòu)我們就可以簡(jiǎn)化為:

BehaviorsTable
    .getInstance()
    .register(Type.INIT)
        .at(Type.NUMBER, Behaviors.NewToken)
        .done()
    .register(Type.NUMBER)
          .at(Type.NUMBER, Behaviors.AppendValue)
          .at(Type.OPERATOR, Behaviors.NewToken)
          .done()
      .register(Type.OPERATOR)
          .at(Type.NUMBER, Behaviors.NewToken)
          .done();

現(xiàn)在 process 方法我們就可以簡(jiǎn)化為:

public void process() {
    this.setToken(new Token(Type.INIT));
    for (char ch : expression.toCharArray()) {
        Type type = Type.of(ch);
        if (Type.UNKNOWN.equals(type)) {
            throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch));
        }
        BehaviorsTable
            .getInstance()
               // 根據(jù)當(dāng)前 Token 類型獲取對(duì)應(yīng)的處理對(duì)策
            .get(this.token.getType())
                  // 獲取當(dāng)前字符所屬的處理行為
                .is(type)
                .apply(type, ch);
        System.out.println(token);
    }
}
我們?cè)谠创a里做了一些改動(dòng),請(qǐng)參考文章底部全部代碼。
讓我們測(cè)試一下
public static void main(String ... args) {
    Scanner scanner = Scanner.getInstance();
  
    scanner.from("10+1").process();
      /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * {type: NUMBER, value: `+`}
       * {type: NUMBER, value: `1`}
       */
  
      scanner.from("10 +1").process();
       /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * Exception in thread "main" java.lang.RuntimeException: ` ` 并不屬于期望的字符類型
       */
      
      scanner.from("10++1").process();
      /**
       * {type: NUMBER, value: `1`}
       * {type: NUMBER, value: `10`}
       * {type: OPERATOR, value: `+`}
    * Exception in thread "main" java.lang.RuntimeException: Invalid Character: `+` for Token `OPERATOR`
       */
}

現(xiàn)在看起來一切正常,但是別忘了 Scanner 的工作是將輸入的字符串分割為 Token 序列,因此我們需要讓 process 方法返回處理后的 LinkedList 序列。

為此我們需要將每次新生成的 Token 保存下來:

public class Scanner {
    private LinkedList tokens;
      private Token token;
  
      public void addToken(Token token) {
        this.token = token;
          this.tokens.add(token);
    }
  
       public LinkedList process() {
        this.setToken(new Token(Type.INIT));
        for (char ch : expression.toCharArray()) {
            Type type = Type.of(ch);
            if (Type.UNKNOWN.equals(type)) {
                throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch));
            }
            BehaviorsTable
                    .getInstance()
                    .get(this.token.getType())
                    .is(type)
                    .apply(type, ch);
        }
        return this.tokens;
    }
  
       public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("10*5+1");
          LinkedList tokens = scanner.process();
          for (Token token : tokens) {
              System.out.println(token);
        }
    }
}

記得將 Behaviors 初始化部分中 NewToken 里的行為修改一下:

        behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
            token = new Token(type);
            token.appendValue(ch);
              //scanner.setToken(token);
            scanner.addToken(token);
        });

現(xiàn)在再看看結(jié)果:

{type: NUMBER, value: `10`}
{type: OPERATOR, value: `*`}
{type: NUMBER, value: `5`}
{type: OPERATOR, value: `+`}
{type: NUMBER, value: `1`}

看起來一切都如我們所愿!現(xiàn)在離最初的目標(biāo)只剩下空格的處理了,得益于我們抽象了行為 Behaviors,我們只需要在 Type 中注冊(cè)空格,然后為 BehaviorsTable 注冊(cè)各種類型下對(duì)空格的處理就行了:

public enum Type {
    SPACE;
      
      public Tpye of(char ch) {
        if (" " == ch) return SPACE;
    }
}

public enum Behaviors {
    Continue;
  
      static {
        behaviorMap
              .put(Behaviors.Continue, (token, type, ch) -> {
                // 留空就行了
            })
    }
}

public class Scanner {
       static {
        BehaviorsTable
                .getInstance()
                .register(Type.INIT)
                    .at(Type.NUMBER, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done()
                .register(Type.NUMBER)
                    .at(Type.NUMBER, Behaviors.AppendValue)
                    .at(Type.OPERATOR, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done()
                .register(Type.OPERATOR)
                    .at(Type.NUMBER, Behaviors.NewToken)
                       .at(Type.SPACE, Behaviors.Continue)
                    .done();
    }
}
還缺什么呢

我們現(xiàn)在完成的掃描器實(shí)際上無法識(shí)別出 1 1 + 0 是個(gè)錯(cuò)誤的表達(dá)式,它會(huì)解析出如下序列:

{type: NUMBER, value: 11}
{type: OPERATOR, value: +}
{type: NUMBER, value: 0}

我個(gè)人希望這部分工作往上層放,由消化 Token 序列的調(diào)用者通過模式匹配的方式去驗(yàn)證,不過這樣的話,Type.SPACE 的處理就不能隨意 Continue 了,有興趣的話看官可以自行嘗試一下 : P

全部代碼

一個(gè)小嘗試,就不傳 Github 了,直接放這兒吧 (其實(shí)就是懶...

Scanner.java
public class Scanner {
  
   private static volatile Scanner instance;

   public static Scanner getInstance() {
      if (Scanner.instance == null) {
         synchronized ( Scanner.class ) {
            if (Scanner.instance == null) {
               Scanner.instance = new Scanner();
            }
         }
      }
      return Scanner.instance;
   }

   static {
     // 注冊(cè)行為表
      BehaviorsTable
            .getInstance()
            // 注冊(cè) INIT 狀態(tài)的行為表
            .register(Type.INIT)
               .at(Type.NUMBER, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done()
            .register(Type.NUMBER)
               .at(Type.NUMBER, Behaviors.AppendValue)
               .at(Type.OPERATOR, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done()
            .register(Type.OPERATOR)
               .at(Type.NUMBER, Behaviors.NewToken)
               .at(Type.SPACE, Behaviors.Continue)
               .done();
   }

   private String expression;
   private LinkedList tokens;
   private Token token;

   public Scanner from(String expression) {
      this.expression = expression;
      this.tokens = new LinkedList<>();
      return this;
   }
  
   public void setToken(Token token) {
      this.token = token;
   }

   public Token getToken ( ) {
      return token;
   }
  
   public LinkedList process() {
      this.setToken(new Token(Type.INIT));
      for (char ch : expression.toCharArray()) {
         Type type = Type.of(ch);
         if (Type.UNKNOWN.equals(type)) {
            throw new RuntimeException(String.format("`%c` 并不屬于期望的字符類型", ch));
         }
         BehaviorsTable
               .getInstance()
                   // 獲取當(dāng)前 Token 類型所適用的行為表
               .get(this.token.getType())
                       // 獲取當(dāng)前字符所適用的行為
                       .is(type)
                       .apply(type, ch);
      }
      return this.tokens;
    }

   public void addToken(Token token) {
     // 更新一下當(dāng)前 Token
      this.token = token;
      this.tokens.add(token);
   }
  
   public static void main(String ... args) {
        Scanner scanner = Scanner.getInstance().from("10 * 5+1");
       LinkedList tokens = scanner.process();
       for (Token token : tokens) {
          System.out.println(token);
      }
    }
}
Type.java
// Token 類型枚舉
public enum Type {
    INIT,        // 初始化時(shí)使用
    SPACE,        // 空格
    NUMBER,        // 數(shù)字
    OPERATOR,    // 操作符
    UNKNOWN;    // 未知類型
    
    public static Type of(char ch) {
        if (" " == ch) {
            return SPACE;
        }
        if ("0" <= ch && ch <= "9") {
            return NUMBER;
        }
        if ("+-*/".indexOf(ch) != -1) {
            return OPERATOR;
        }
        return UNKNOWN;
    }
}
Token.java
public class Token {
    // 一個(gè) Token 的類型一旦確定,就不可能再改變。
    private final Type type;
    // 用以存儲(chǔ) Token 的值。
    private final StringBuffer value;
    
    public Token(Type type) {
        this.type = type;
        this.value = new StringBuffer();
    }
    
      // 向 value 中追加字符
    public void appendValue(char ch) {
        this.value.append(ch);
    }
    
    public String getValue() {
        return this.value.toString();
    }
    
    public Type getType() {
        return this.type;
    }
    
    @Override
    public String toString() {
        return String.format("{type: %s, value: `%s`}",
            this.getType().name(),
            this.getValue());
    }
}
Behavior.java
public interface Behavior {
    /**
     * 將行為抽象出來
     * @param token 當(dāng)前的 token
     * @param type 讀入字符的類型
     * @param ch 讀入的字符
     */
    void apply(Token token, Type type, char ch);
}
Behaviors.java
// 預(yù)設(shè)行為
public enum Behaviors {
   NewToken,        // 新建一個(gè)指定類型的 Token, 將當(dāng)前字符保存到新 Token
   AppendValue,        // 將當(dāng)前字符追加到當(dāng)前 Token 的值中
   Continue,        // 跳過當(dāng)前字符
   InvalidCharacter; // 當(dāng)前 Token 類型所不期望的字符類型,會(huì)拋出一個(gè)異常

      // 持有一個(gè)引用就不用老是調(diào)用 getInstance()。
   private static final Scanner scanner;
      // 為預(yù)設(shè)行為指定行為內(nèi)容
   private static final HashMap behaviorMap;

   static {
      scanner = Scanner.getInstance();
      behaviorMap = new HashMap<>();
     // 指定 NewToken,行為邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.NewToken, (token, type, ch) -> {
         token = new Token(type);
         token.appendValue(ch);
         scanner.addToken(token);
      });
     // 指定 AppendValue,行為邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.AppendValue, (token, type, ch) -> {
         token.appendValue(ch);
      });
     // 指定 Continue,行為邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.Continue, (token, type, ch) -> {});
     // 指定 InvalidCharacter,行為邏輯參見枚舉值說明
      behaviorMap.put(Behaviors.InvalidCharacter, (token, type, ch) -> {
         throw new RuntimeException(String
                                    .format("Invalid Character: `%c` for Token `%s`",
                                            ch, token.getType().name()));
      });
   }

   public void apply(Type type, char ch) {
     // 獲取預(yù)設(shè)行為
      behaviorMap.get(this)
            // 向行為中傳遞當(dāng)前 Token, 當(dāng)前字符類型,當(dāng)前字符
            .apply(scanner.getToken(), type, ch);
   }
}
BehaviorsMap.java
// 保存某一字符類需要執(zhí)行何種預(yù)設(shè)行為的映射關(guān)系
public class BehaviorsMap {
    private final HashMap map;
   public BehaviorsMap() {
        this.map = new HashMap();
    }
   
    /**
     * 注冊(cè)指定類型所需的預(yù)設(shè)行為
     * @param type 指定類型
     * @param behaviors 指定所需的預(yù)設(shè)行為
     */
   public BehaviorsMap at(Type type, Behaviors behaviors) {
        this.map.put(type, behaviors);
       return this;
    }

  // 注冊(cè)完后回退操作域到 BehaviorsTable
    public BehaviorsTable done() {
      return BehaviorsTable.getInstance();
   }

  // 獲取指定類型的預(yù)設(shè)行為
   public Behaviors is (Type type) {
      Behaviors behaviors = this.map.get(type);
      if (behaviors == null) {
        // 如果沒有注冊(cè),那么使用 InvalidCharacter 預(yù)設(shè)行為,因?yàn)槌霈F(xiàn)了非預(yù)期的字符類型
         behaviors = Behaviors.InvalidCharacter;
      }
      return behaviors;
   }
}
BehaviorsTable.java
// 行為表
public class BehaviorsTable {

    private static volatile BehaviorsTable instance;

    public static BehaviorsTable getInstance() {
        if (BehaviorsTable.instance == null) {
            synchronized ( BehaviorsTable.class ) {
                if (BehaviorsTable.instance == null) {
                    BehaviorsTable.instance = new BehaviorsTable();
                }
            }
        }
        return BehaviorsTable.instance;
    }

    private final HashMap map;

    public BehaviorsTable () {
        this.map = new HashMap<>();
    }

      // 注冊(cè)指定當(dāng)前類型,返回一個(gè)空的 BehaviorsMap 來注冊(cè)預(yù)設(shè)行為
    public BehaviorsMap register(Type type) {
        BehaviorsMap behaviorsMap = new BehaviorsMap();
        this.map.put(type, behaviorsMap);
        return behaviorsMap;
    }

   // 獲取指定當(dāng)前類型的 BehaviorsMap
    public BehaviorsMap get(Type type) {
        return this.map.get(type);
    }
}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70961.html

相關(guān)文章

  • Java 虛擬機(jī)總結(jié)給面試你(中)

    摘要:驗(yàn)證過程驗(yàn)證過程的目的是為了確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。二虛擬機(jī)字節(jié)碼執(zhí)行引擎虛擬機(jī)的執(zhí)行引擎自行實(shí)現(xiàn),可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系。 本篇博客主要針對(duì)Java虛擬機(jī)的類加載機(jī)制,虛擬機(jī)字節(jié)碼執(zhí)行引擎,早期編譯優(yōu)化進(jìn)行總結(jié),其余部分總結(jié)請(qǐng)點(diǎn)擊Java虛擬總結(jié)上篇 。 一.虛擬機(jī)類加載機(jī)制 概述 虛擬機(jī)把描述類的數(shù)據(jù)從Clas...

    MRZYD 評(píng)論0 收藏0
  • 深入理解JavaScript

    摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...

    myeveryheart 評(píng)論0 收藏0
  • 夯實(shí)基礎(chǔ)-作用域與閉包

    摘要:作用域分類作用域共有兩種主要的工作模型。換句話說,作用域鏈?zhǔn)腔谡{(diào)用棧的,而不是代碼中的作用域嵌套。詞法作用域詞法作用域中,又可分為全局作用域,函數(shù)作用域和塊級(jí)作用域。 一篇鞏固基礎(chǔ)的文章,也可能是一系列的文章,梳理知識(shí)的遺漏點(diǎn),同時(shí)也探究很多理所當(dāng)然的事情背后的原理。 為什么探究基礎(chǔ)?因?yàn)槟悴蝗ッ嬖嚹憔筒恢阑A(chǔ)有多重要,或者是說當(dāng)你的工作經(jīng)歷沒有亮點(diǎn)的時(shí)候,基礎(chǔ)就是檢驗(yàn)?zāi)愫脡牡囊豁?xiàng)...

    daydream 評(píng)論0 收藏0
  • 還擔(dān)心面試官問閉包?

    摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會(huì)得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會(huì)消失,函數(shù)依然保持有作用域的閉包。 網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 評(píng)論0 收藏0
  • 深入編譯器——第一部分:詞法解析和Scanner(介紹ECMAScript詞法規(guī)范和TypeScr

    摘要:詞法分析對(duì)構(gòu)成源程序的字符流進(jìn)行掃描然后根據(jù)構(gòu)詞規(guī)則識(shí)別單詞也稱單詞符號(hào)或符號(hào)。語義分析是編譯過程的一個(gè)邏輯階段語義分析的任務(wù)是對(duì)結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查進(jìn)行類型審查,審查抽象語法樹是否符合該編程語言的規(guī)則。 1. 文章的內(nèi)容和主題 我對(duì)編譯器的深入了解起源于一條推特中的問題:Angular是如何用Angular預(yù)先編譯器(AOT)對(duì)靜態(tài)代碼進(jìn)行解析工作的。在進(jìn)行一些...

    pingan8787 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<