簡體   English   中英

將一種語言翻譯成另一種語言的一般方法是什么?

[英]What is a general approach for transpiling one language to another?

我想將 JavaScript 轉換為 LinkScript。 我是這樣開始的:

const acorn = require('acorn')
const fs = require('fs')

const input = fs.readFileSync('./tmp/parse.in.js', 'utf-8')

const jst = acorn.parse(input, {
  ecmaVersion: 2021,
  sourceType: 'module'
})

fs.writeFileSync('tmp/parse.out.js.json', JSON.stringify(jst, null, 2))

const linkScriptText = generateLinkScriptText(convertToLinkScriptAst(jst))

fs.writeFileSync('tmp/parse.out.link', linkScriptText)

function convertToLinkScriptAst(jst) {
  const lst = {}
  switch (jst.type) {
    case 'Program':
      convertProgram(jst, lst)
      break
  }
  return lst
}

function convertProgram(jst, lst) {
  lst.zones = []
  jst.body.forEach(node => {
    switch (node.type) {
      case 'VariableDeclaration':
        convertVariableDeclaration(node).forEach(vnode => {
          lst.zones.push(vnode)
        })
        break
      case 'ExpressionStatement':

        break
      default: throw JSON.stringify(node)
    }
  })
}

function convertVariableDeclaration(jst) {
  return jst.declarations.map(dec => {
    switch (dec.type) {
      case 'VariableDeclarator':
        return convertVariableDeclarator(jst.kind, dec)
        break
      default: throw JSON.stringify(dec)
    }
  })
}

function convertVariableDeclarator(kind, jst) {
  return {
    type: 'host',
    immutable: kind === 'const',
    name: jst.id.name,
    value: convertVariableValue(jst.init)
  }
}

function convertVariableValue(jst) {
  if (!jst) return

  switch (jst.type) {
    case 'Literal':
      return convertLiteral(jst)
      break
  }
}

function convertLiteral(jst) {
  switch (typeof jst.value) {
    case 'string':
      return {
        type: 'string',
        value: jst.value
      }
    case 'number':
      return {
        type: 'number',
        value: jst.value
      }
    default: throw JSON.stringify(jst)
  }
}

function generateLinkScriptText(lst) {
  const text = []
  lst.zones.forEach(zone => {
    switch (zone.type) {
      case 'host':
        generateHost(zone).forEach(line => {
          text.push(line)
        })
        break
    }
  })
  return text.join('\n')
}

function generateHost(lst) {
  const text = []
  if (lst.value) {
    switch (lst.value.type) {
      case 'string':
        text.push(`host ${lst.name}, text <${lst.value.value}>`)
        break
      case 'number':
        text.push(`host ${lst.name}, size ${lst.value.value}`)
        break
    }
  } else {
    text.push(`host ${lst.name}`)
  }
  return text
}

基本上,您將 JS 解析為 AST,然后以某種方式將此 AST 轉換為目標語言的 AST(在本例中為 LinkScript)。 然后將輸出的 AST 轉換為文本。 問題是,這樣做的一般策略是什么? 好像挺難的。

更詳細地說,我需要知道您可以在 JavaScript 中創建的所有結構類型,以及您可以在 LinkScript 中創建的所有結構類型,以及如何映射到另一個。 在我的腦海中,看着 JS,我可以手動弄清楚相應的 LinkScript 應該是什么樣子。 但是,嘗試以編程方式執行此操作是另一回事,而且我對應該采用的一般方法感到有些迷茫。

首先,盡管我已經做了 10 多年的 JavaScript,但我對 JS AST 不是很了解。 我計划編寫一些示例代碼片段,並使用acorn查看 AST 的外觀。 其次,似乎有太多的組合讓人難以抗拒。

我是否繼續沿着我在上面開始的這條路走下去? 或者是否有更結構化或紀律性更強的方法? 我如何更好地將問題分解為更易於管理的塊?

此外,它並不總是像進行簡單的一對一映射那樣容易。 有時事情的順序會改變。 例如,在 JS 中你可能有:

a = x + y

但在 LinkScript 中,這將是:

call add
  bind a, link x
  bind b, link y
  save a

所以賦值表達式有點顛倒。 在其他情況下它會變得更加復雜。

所以就好像我需要研究每種類型的映射,並就如何進行這種映射提出詳細的計划或算法。 那么似乎我需要研究成千上萬種可能的轉換/映射類型。 所以從這個意義上說,在精神上解決這個問題似乎是一個非常耗時的問題。

有更容易的方法嗎?

很長一段時間(幾年?)我一直想這樣做,但這似乎總是像我暗示的那樣極其艱巨的任務。 我認為這是因為我沒有在腦海中清楚地看到我可以接收 AST 的所有不同方式/角度,而且我不知道如何將其歸結為我可以看到的東西。

除了弄清楚如何進行每種類型的映射/轉換之外,我還應該有一些可以擴展的體面代碼。 這通常是我的強項(用簡單的 API 提出干凈的代碼),但在這里我很掙扎,因為是的,我還沒有看到完整的畫面。

編寫轉譯器是一項非常艱巨的工作……但出於各種原因,JavaScript 工作流已經充滿了轉譯器,因此有很多工具可以提供幫助。

如果您的目標語言看起來像 JavaScript,那么您可以將您的轉譯器編寫為 Babel 的插件: https ://babeljs.io/

否則,也許從jscodeshift開始,它將為您提供一個易於訪問的 AST。

許多開源 javascript 工具,如eslint ,也有 javscript 解析器,您可以稍微費力地提取。

另請參閱AST Explorer

一旦你有了一個 AST,你通常會遞歸地處理它,可能遵循訪問者模式,將每個 AST 節點轉換為等效的目標結構。 然后也許可以通過窺視孔優化來簡化生成的 AST。 然后最后序列化它。 jscodeshift 帶有一個 javascript 序列化程序,您可以用自己的序列化程序替換它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM