如何將JavaScript轉化成Swift?(一)

LuckyRoc發表於2019-03-29

最近遇到了一個詭異的需求:

將一個弱型別語言JavaScript轉化成強型別語言Swift

方案一:

強轉,將JavaScript的語法和Swift的語法一一對應

在Js中 一個方法的關鍵詞是 function 在Swift中是 func,我們遍歷一下Js程式碼,將所有的 function 轉化成 func

嗯。。。 感覺太傻了。。。沒有一點作為一個程式猿的追求

方案二:

將Js轉化成AST,將Js的AST轉化成Swift的AST,然後再轉回Swift

嗯。。。這個貌似牛逼哄哄,但是遇到了幾個難點:

1、Js的AST和Swift的AST很難找到對應關係

JavaScript中定義一個a變數:

var a = 1

Swift中定義一個a變數:

var a = 1

他們的AST分別如下:

JavaScript的AST

Swift的AST

兩者的語法描述一模一樣,但是AST卻相差很多,很難找到差異性

2、JavaScript中通過 escodegen 這個庫 將AST重新生成為原始碼,但是在Swift和Java中並未找到相應的官方庫或者第三方庫來進行轉化

3、JavaScript中有ES5、ES6,我們可以編寫ES6的語法然後通過Bebal將ES6翻譯成ES5,用到就是 JavaScript 和 AST 之間的相互轉換,但是Swift並中沒有類似的需求,所以相應的資料程式碼極少

方案三:

將Js的AST轉化成Swift

既然不能將Swift的AST轉化成Swift,可不可以將Js的AST轉化成Swift呢?

如何操作?:

這裡借鑑了JS Parser的三板斧

1.通過 esprima 把原始碼轉化為AST

2.通過 estraverse 遍歷並更新AST

3.通過 escodegen 將AST重新生成原始碼

簡單說一下原理:

  • 首先通過 esprima 將js 原始碼翻譯成 AST,
  • 通過 estraverse 遍歷AST 補充一些特徵(比如型別)
  • 修改 escodegen 原始碼,生成Swift原始碼

修改 escodegen 原始碼

新建一個Js的專案,安裝 esprima、escodegen依賴,

新建 test.js 需要解析的js程式碼:

function testFunc() {
    var a = 1
    var b = "1"
    var c  = false
    if (a<20) {
      b = "Good day";
    }
    console.log(b)
    var car = {type:"Fiat", model:500, color:"white"};
    console.log(car.type)
}
  
複製程式碼

新建 swift.js 用來讀取 test.js 程式碼,生成 test.swift 檔案:

const esprima = require('esprima');
const escodegen = require("escodegen");
var fs = require("fs")

// 讀取test.js程式碼
var data = fs.readFileSync('test.js');
let code =  data.toString()

// 解析js的語法
let tree = esprima.parseScript(code);

// 解析ast
let transformCode = escodegen.generate(tree);

// 生成swift檔案
fs.writeFile('test.swift', transformCode,  function(err) {
   if (err) {
       return console.error(err);
   }
});
複製程式碼

進行編譯。。。

node swift.js

下面開始修改 escodegen 裡面的 escodegen.js 檔案

將 function 替換成 func

 FunctionDeclaration: function (stmt, flags) {
             return [
                 generateAsyncPrefix(stmt, true),
                 'function',
                 generateStarSuffix(stmt) || noEmptySpace(),
                 stmt.id ? generateIdentifier(stmt.id) : '',
                 this.generateFunctionBody(stmt)
             ];
        },
複製程式碼

這段程式碼是用來處理 function 節點,我們只需要將 function 修改成 func 即可,很簡單!

Js是弱型別,如何判斷型別?

VariableDeclarator: function (stmt, flags) {
            var itemFlags = (flags & F_ALLOW_IN) ? E_TTT : E_FTT;
            console.log("+++++++++++++++++ 執行 VariableDeclarator+++++++++++++++++")
            // 增加資料型別
            // console.log(stmt.mold.name)
            if (stmt.init) {
                 if (stmt.mold.name) {
                     return [
                         this.generateExpression(stmt.id, Precedence.Assignment, itemFlags),
                         space,
                         '=',
                         space,
                         this.generateExpression(stmt.init, Precedence.Assignment, itemFlags)
                     ];
                 }
                ];
            }
複製程式碼

這是處理變數的一個方法, 這段程式碼的返回結果是:a = 1 , 我們只需要在 a 前面加上 型別即可,這裡用 Js的 typeof 方法進行判斷,然後將 number、string、boolean 和swift 的Int、String、Bool進行對映

如何將 console.log 修改成 print

// 進行字串替換 
for (let i = 0; i < result.length; i++) {
    const element = result[i];
    if (element.indexOf("console.log")!=-1) {
        result[i] = element.replace(/console.log/, "print")
    }
}
複製程式碼

比較簡單粗暴。。。

如何將Js的對應翻譯成 Swift的物件?

這個稍微複雜,因為Js弱型別的特性,他不需要額外去寫一個類定一個物件,但是Swift需要,所以這裡我們需要讀取Js的物件,然後生成一個類

在 Js 中 描述一個物件

var car = {type:"Fiat", model:500, color:"white"}
複製程式碼

在Swift中描述一個物件

class Car: NSObject {
    var type: String = ""
    var model: Int = 0
    var color: String = ""
    init(type: String, model: Int, color: Int) {
        self.type = type
        self.model = model
        self.color = color
    }
}

let car = Car(type: "Fiat", model: 500, color: "white")
複製程式碼

在 ObjectExpression 方法中 可以通過

if (expr.properties) {
    let properties  = expr.properties
    for (let i = 0; i < properties.length; i++) {
        const element = properties[i];
        // 生成對應的檔案
        console.log(element.key.name)
    }
}
複製程式碼

將 var car = {type:"Fiat", model:500, color:"white"} 中的type、model、color讀取到,然後生成對應的Swift檔案

剩下的就是將

var car = {type:"Fiat", model:500, color:"white"}
複製程式碼

替換成

let car = Car(type: "Fiat", model: 500, color: "white")
複製程式碼

只需要生成物件的時候記錄一下類名,將 ‘{’ 替換成 ‘Car(’ ,將 ‘)’ 替換成 ‘}’即可

生成物件的程式碼:

// 生成類名
            let className =  getClassName(expr) 
            var swiftObject = 'class ' + className + ': NSObject { \n'

            if (expr.properties) {
                let properties  = expr.properties
                for (let i = 0; i < properties.length; i++) {
                    const element = properties[i];
                    // 生成對應的檔案
                    swiftObject += space + space + "var " + element.key.name + ': ' + typeOfSwift(element.value.value) + '\n'
                }
            }

            swiftObject += '}'

            fs.writeFile('./Swift_Code/Model/' + className + '.swift', swiftObject,  function(err) {
                console.log(err)
                if (err) {
                    return console.error(err);
                }
             });

複製程式碼

成果

完成了上述的翻譯就可以完美的將 test.js 程式碼翻譯成 test.swift 啦

func testFunc() {
    var a:Int = 1;
    var b:String = '1';
    var c:Bool = false;
    if (a < 20) {
        b = 'Good day';
    }
    print(b);
    var car = Car(
        type: 'Fiat',
        model: 500,
        color: 'white'
    );
    print(car.type);
}
複製程式碼

for迴圈如何翻譯?Js的網路請求如何翻譯成Swift

如何將JavaScript轉化成Swift?(二)

未完待續。。。。

相關文章