2017-04-26 84 views
1

我想擴展javascript以添加自定義類型檢查。擴展Javascript語法以添加鍵入

例如

function test(welcome:string, num:integer:non-zero) { 
    console.log(welcome + num) 
} 

這將彙編成:

function test(welcome, num) { 
    if(Object.prototype.toString.call(welcome) !== "[object String]") { 
     throw new Error('welcome must be a string') 
    } 

    if (!Number.isInteger(num)) { 
     throw new Error('num must be an integer') 
    } 

    console.log(welcome + num) 
} 

什麼是這樣做的最簡單的方法是什麼?

到目前爲止,我已經看了看:

  • sweet.js(在線文檔看上去過時了,因爲我認爲它會通過某種內部重寫的)
  • esprima和escodegen(不知道在哪裏開始)
  • 手動解析使用正則expressons
+1

你有沒有考慮打字稿? –

+0

你最後的「建議」不會讓你走得太遠。你出門做了一些嚴重的「黑客行爲」(我認爲這就是js文化所說的),所以可怕的API和過時的文檔不應該嚇到你:) –

+0

@AndyLamb是的,我有,我腦海裏有什麼儘管如此,它更強大,更有表現力。例如function(allWages:array [integer:not-negative]:no-empty)...然後每個應用程序也有自定義的棋子。 –

回答

1

評估所有的各種選項之後,使用sweet.js似乎是最好的解決方案。工作起來仍然相當困難(而且我可能以錯誤的方式做事),但爲了防止有人想要做類似的事情,這裏是我的解決方案。

'use strict' 

    syntax function = function(ctx) { 
     let funcName = ctx.next().value; 
     let funcParams = ctx.next().value; 
     let funcBody = ctx.next().value; 

     //produce the normal params array 
     var normalParams = produceNormalParams(funcParams) 

     //produce the checks 
     var paramChecks = produceParamChecks(funcParams) 

     //produce the original funcBody code 

     //put them together as the final result 

     var params = ctx.contextify(funcParams) 

     var paramsArray = [] 
     for (let stx of params) { 
      paramsArray.push(stx) 
     } 

     var inner = #`` 
     var innerStuff = ctx.contextify(funcBody) 
     for (let item of innerStuff) { 
      inner = inner.concat(#`${item}`) 
     } 

     var result = #`function ${funcName} ${normalParams} { 
      ${paramChecks} 
      ${inner} 
     }` 

     return result 

     function extractParamsAndParamChecks(paramsToken) { 
      var paramsContext = ctx.contextify(paramsToken) 

      //extracts the actual parameters 
      var paramsArray = [] 
      var i = 0; 
      var firstItembyComma = true 
      for (let paramItem of paramsContext) { 
       if (firstItembyComma) { 
        paramsArray.push({ 
         param: paramItem, 
         checks: [] 
        }) 
        firstItembyComma = false 
       } 

       if (paramItem.value.token.value === ',') { 
        firstItembyComma = true 
        i++ 
       } else { 
        paramsArray[i].checks.push(paramItem.value.token.value) 
       } 
      } 

      for (var i = 0; i < paramsArray.length; i++) { 
       var checks = paramsArray[i].checks.join('').split(':') 
       checks.splice(0, 1) 
       paramsArray[i].checks = checks 
      } 

      return paramsArray 
     } 

     function produceNormalParams(paramsToken) { 
      var paramsArray = extractParamsAndParamChecks(paramsToken) 

      //Produces the final params #string 
      var inner = #`` 
      var first = true 
      for (let item of paramsArray) { 
       if (first === true) { 
        inner = inner.concat(#`${item.param}`) 
       } else { 
        inner = inner.concat(#`,${item.param}`) 
       } 
      } 
      return #`(${inner})` 
     } 

     function produceParamChecks(paramsToken) { 
      var paramsArray = extractParamsAndParamChecks(paramsToken) 

      var result = #`` 
      for (let paramObject of paramsArray) { 
       var tests = produceChecks(paramObject) 
       result = result.concat(#`${tests}`) 
      } 
      return result 
     } 

     function produceChecks(paramObject) { 
      var paramToken = paramObject.param 
      var itemType = paramObject.checks[0] 
      var checks  = paramObject.checks 

      if (itemType === undefined) return #`` 

      if (itemType === 'array') { 
       return #`if (Object.prototype.toString.call(${paramToken}) !== "[object Array]") throw new Error('Must be array:' + ${paramToken})` 
      else { 
       throw new Error('item type not recognised: ' + itemType) 
      } 
     } 
    }