最近重新撿起了一年前學的編譯原理(當時甚至沒概念)
用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,不過這東西寫着寫着就變成坑了,代碼之間太緊密了,所以打算過一段時間再寫一個
個人認爲網上那些一大堆一大堆說的很煩,代碼還得自己拼接,所以就直接報代碼了,註釋很全,不用我說一大堆自己也能看懂