用JavaScript實現DFA詞法分析器

最近重新撿起了一年前學的編譯原理(當時甚至沒概念)

DFA確定有窮自動機寫了個很簡陋的詞法分析器

僞代碼如下:

K -> number = 123;
K -> object = {" \\"a"}
K -> string = "abc"

Node.js執行後得到的結果如下

[ { type: 'Identifier', line: 2, position: 3, value: 'K' },
  { type: 'Dtype', line: 2, position: 3, value: '->' },
  { type: 'Variable', line: 2, position: 10, value: 'number' },
  { type: 'Assignment', line: 2, position: 10, value: '=' },
  { type: 'Number', line: 2, position: 15, value: '123' },
  { type: 'Identifier', line: 3, position: 3, value: 'K' },
  { type: 'Dtype', line: 3, position: 3, value: '->' },
  { type: 'Variable', line: 3, position: 10, value: 'object' },
  { type: 'Assignment', line: 3, position: 10, value: '=' },
  { type: 'Block', line: 3, position: 18, value: '{" \\"a"}' },
  { type: 'Identifier', line: 4, position: 3, value: 'K' },
  { type: 'Dtype', line: 4, position: 3, value: '->' },
  { type: 'Variable', line: 4, position: 10, value: 'string' },
  { type: 'Assignment', line: 4, position: 11, value: '=' },
  { type: 'String', line: 4, position: 17, value: '"abc"' } ]

這是一個根據僞代碼寫出詞法分析器,話不多說,直接報代碼:

var code = `
	K->number= 12.3;
	K->object={" \\"a"}
	K->string = "abc"
	`;
// 解析函數,懶得重寫結構了
// 這個東西也只是爲了測試而寫的
function Lexed(code){
	// 類型數組,儲存類型
	var varKeyWords = ["number","string","object"];
	// 臨時字符組
	var temp = []
	// 返回解析得到的字典組
	var result = [];
	// 代表當前狀態,0爲type.none
	var state = 0;
	// 當前行數
	var line = 1;
	// inde解析到的當前位置
	var i = 0;
	// 記錄當前行的字符位置
	var linePos = 0;
	// 當前位置的字符,方便引用
	var ch;
	// 字符串長度
	var codeLen = code.length;
	// 判斷是否爲某個type,返回字典組會記錄type以保證後面的AST抽象語法樹生成
	var is = {
		// 是否爲數字
		"number": function(ch){
			return /[0-9]/.test(ch);
		},
		// 是否爲字符串,即 ("xx\"x") 或者 ("xxx") 這樣結構的字符串
		"string": function(ch){
			return /^\"([\s\S]*)[^\\]\"$/.test(ch);
		},
		// 是否爲標識符,即變量允許的名稱
		"identifier": function(ch){
			return /[a-zA-Z$_]/.test(ch);
		},
		// 是否爲變量類型,會檢查varKeyWords變量類型關鍵字組
		"variable": function(str){
			for(var i in varKeyWords){
				if(varKeyWords[i] === str){
					return true;
				}
			}
			return false;
		},
		// 是否爲空格或製表符
		"space": function(ch){
			return /[ \t]/.test(ch);
		},
		// 是否爲符號,這裏的符號還連帶了其處理方式,還可以放入函數,在處理前執行
		"symbol": function(ch,fn){
			var result = false;
			if(typeof fn === 'function'){
				fn(ch);
			}
			switch (ch) {
				case '\"':
					// 存入臨時字符組
					temp.push(ch);
					// 改變狀態爲字符串解析
					state = type.string;
					// 改爲true
					result = true;
					// 下同
				break;
				case '(':
					temp.push(ch);
					state = type.param;
					result = true;
				break;
				case '{':
					temp.push(ch);
					state = type.block;
					result = true;
				break;
				case ',':
					// 逗號就直接寫入字符
					add.division(ch);
					result = true;
				break;
				case '=':
					// 賦值也一樣
					add.assignment();
					result = true;
				break;
				case '-':
					// 如果是 -> 類型聲明符號
					if(code[i+1] === '>'){
						// 存入
						add.dtype();
						// 因爲有兩個字符所以index+1
						i++;
						result = true;
					}
				break;
			}
			// 返回
			return result;
		},
		// 是否到結尾
		"end": function(){
			// 如果當前index+1就是代碼的長度了,返回true
			return i+1 === codeLen;
		}
		
	};
	// 添加,爲了方便使用
	var add = {
		// 數字類型
		"number": function(str){
			result.push({
				// 寫入類型
				"type": "Number",
				// 寫入行號
				"line": line,
				// 寫入當前行的位置,方便差錯
				"position": linePos,
				// 寫入值
				"value": str
			});
			// 下同
		},
		"string": function(str){
			result.push({
				"type": "String",
				"line": line,
				"position": linePos,
				"value": str
			});
		},
		"identifier": function(str){
			result.push({
				"type": "Identifier",
				"line": line,
				"position": linePos,
				"value": str
			});
		},
		"variable": function(str){
			result.push({
				"type": "Variable",
				"line": line,
				"position": linePos,
				"value": str
			});
		},
		// 如果是逗號分隔符
		"division": function(str){
			result.push({
				"type": "Division",
				"line": line,
				"position": linePos,
				"value": str
			});
		},
		// 是定義類型
		"dtype": function(){
			result.push({
				"type": "Dtype",
				"line": line,
				"position": linePos,
				"value": "->"
			});
		},
		// 是賦值的=
		"assignment": function(){
			result.push({
				"type": "Assignment",
				"line": line,
				"position": linePos,
				"value": "="
			});
		},
		"block": function(str){
			result.push({
				"type": "Block",
				"line": line,
				"position": linePos,
				"value": str
			});
		},
		"param": function(str){
			result.push({
				"type": "Param",
				"line": line,
				"position": linePos,
				"value": str
			});
		}
	};
	// 狀態
	var type = {
		// 無狀態
		"none": 0,
		// 是標識符
		"identifier": 1,
		// 是參數
		"param": 2,
		// 是作用域
		"block": 3,
		// 詞法錯誤
		"error": 4,
		// 字符串
		"string": 5,
		// 數字
		"number": 6
	};
	// 爲了複用寫的,不是權宜之策,只是懶得重寫代碼結構了
	var running = {
		// 是標識符
		"identifier": function(){
			// 字符組轉換成字符串
			temp = temp.join("");
			// 如果是變量類型
			if(is.variable(temp)){
				// 寫入
				add.variable(temp);
			// 否則就一定是標識符
			}else{
				// 寫入
				add.identifier(temp);
			}
			// 如果是換行
			if(ch === '\n'){
				// 行數+1
				line++;
				// 當前行位置重置
				linePos = 0;
			}
			// 字符組重置
			temp = [];
			// 狀態變爲空
			state = type.none;
		},
		// 如果是字符
		"string": function(){
			// 直接寫入
			add.string(temp.join(""));
			if(ch === '\n'){
				line++;
				linePos = 0;
			}
			temp = [];
			state = type.none;
		},
		// 如果是數字
		"number": function(){
			// 直接寫入
			add.number(temp.join(""));
			if(ch === '\n'){
				line++;
				linePos = 0;
			}
			temp = [];
			state = type.none;
		},
		// 如果是塊級作用域
		"block": function(){
			// 直接寫入
			add.block(temp.join(""));
			if(ch === '\n'){
				line++;
				linePos = 0;
			}
			temp = [];
			state = type.none;
		},
		// 如果是參數,即'('和')'
		"param": function(){
			// 直接寫入
			add.param(temp.join(""));
			if(ch === '\n'){
				line++;
				linePos = 0;
			}
			temp = [];
			state = type.none;
		},
		// 如果出現解析錯誤
		"error": function(){
			// 報錯,輸出行號,字符位置
			console.error("SyntaxError: " + (linePos-1) + " characters on line " + line);
			// 遍歷結束
			i = codeLen;
			// 返回值變爲undefined
			result = undefined;
		}
	};
	// 開始遍歷
	for(;i < codeLen;i++,linePos++){
		ch = code[i];
		// 檢查狀態
		switch(state){
			// 如果無狀態
			case type.none:
				// 如果是空格和製表符或者是';'
				// 不理會
				if(is.space(ch)||ch === ';');
				// 如果是換行,上同
				else if(ch === '\n'){
					line++;
					linePos = 0;
				// 如果是標識符
				}else if(is.identifier(ch)){
					// 先存入臨時字符組
					temp.push(ch);
					// 改變狀態
					state = type.identifier;
				// 下同
				}else if(is.number(ch)){
					temp.push(ch);
					state = type.number;
				// 如果連那一堆符號都匹配不到
				}else if(!is.symbol(ch)){
					// 報錯
					running.error();
				}
			break;
			// 如果是標識符
			case type.identifier:
				// 因爲之前已經有一個字符爲非數字字符了,所以可以匹配數字
				if(is.identifier(ch)||is.number(ch)){
					// 寫入
					temp.push(ch);
				}else{
					// 如果是空格||換行||代碼結束||';'
					if(is.space(ch)||ch === '\n'||is.end()||ch === ';'){
						// 操作
						running.identifier();
					// 這裏直接檢查是不是在符號組裏
					}else if(is.symbol(ch,running.identifier));
					// 否則
					else{
						// 報錯
						running.error();
					}
				}
			break;
			// 如果是數字
			case type.number:
				// 如果是空字符||代碼結束||換行||';'
				if(is.space(ch)||is.end()||
					ch === '\n'||ch === ';'){
					// 操作
					running.number();
				// 否則
				}else{
					// 是數字或者有點,因爲前面已經匹配過數字了
					// 所以可以匹配點,不過這裏沒做限制,導致能夠匹配
					// 附帶多個點的莫名其妙的東西還不報錯
					if(is.number(ch)||ch === '.'){
						// 寫入
						temp.push(ch);
					// 否則
					}else{
						// 報錯
						running.error();
					}
				}
			break;
			// 如果是字符串
			case type.string:
				// 如果是'"',先寫入字符,檢查是否爲
				// 符合正則匹配的字符串定義標準
				if(ch === '\"'&&temp.push(ch)&&is.string(temp.join(""))){
					// 操作
					running.string();
				// 否則
				}else{
					// 如果代碼結束
					if(is.end()){
						// 報錯,因爲 ("xxx) 這樣是錯誤的語法
						running.error();
					// 否則
					}else{
						// 寫入字符
						temp.push(ch);
					}
				}
			break;
			// 如果是參數
			case type.param:
				// 如果是')',直接寫入字符
				if(ch === ')'&&temp.push(ch)){
					// 操作
					running.param();
				}else{
					// 上同
					if(is.end()){
						running.error();
					}else{
						temp.push(ch);
					}
				}
			break;
			// 如果是塊級作用域
			case type.block:
				// 如果是右花括號},直接寫入
				if(ch === '}'&&temp.push(ch)){
					// 操作
					running.block();
				}else{
					if(is.end()){
						running.error();
					}else{
						temp.push(ch);
					}
				}
			break;
		}
	}
	// 返回
	return result;
}
// 執行
console.log(Lexed(code));

做這個本來是想實現:

k -> object(ss -> string) = {
    a -> number = 1
    private b -> number = 2
    
    return b,ss
}
kk -> object = k("測試")
puts(kk.a)
puts(kk["b"])
puts(kk)



輸出:
    1
    2
    {"b":2,"ss":"測試","$object": {"a":1}}

這樣的語言,只有number,string和能夠作爲函數用的object,不過這東西寫着寫着就變成坑了,代碼之間太緊密了,所以打算過一段時間再寫一個

個人認爲網上那些一大堆一大堆說的很煩,代碼還得自己拼接,所以就直接報代碼了,註釋很全,不用我說一大堆自己也能看懂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章