【问题标题】:What is a general approach for transpiling one language to another?将一种语言转换为另一种语言的一般方法是什么?
【发布时间】:2021-12-27 06:10:39
【问题描述】:

我想将 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 并不是很了解。我正计划编写一些示例 sn-ps 代码并使用 acorn 查看 AST 的外观。其次,似乎有太多东西的组合,让人不知所措。

我会继续沿着我在上面开始的这条路走下去吗?还是有更结构化或更规范的方法?如何更好地将问题分解为更易于管理的块?

此外,它并不总是像进行简单的一对一映射那样容易。有时事情的顺序会改变。例如,在 JS 中你可能有:

a = x + y

但在 LinkScript 中,这将是:

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

所以赋值表达式有点颠倒。在其他情况下会变得更加复杂。

因此,就好像我需要研究每种单独的映射类型,并就如何进行这种映射提出详细的计划或算法。然后似乎我需要研究数千种可能的转换/映射类型。所以从这个意义上说,从心理上来说,这似乎是一个非常耗时的问题。

有没有更简单的方法?

很长一段时间(几年?)我一直想这样做,但就像我暗示的那样,这似乎总是一项极其艰巨的任务。我认为这是因为我没有在脑海中清楚地看到我可以接收 AST 的所有不同方式/角度,而且我不知道如何将其归结为我可以看到的东西。

除了弄清楚如何进行每种类型的映射/转换之外,我还应该有一些可以扩展的不错的代码。这通常是我的强项(用简单的 API 编写干净的代码),但在这里我很挣扎,因为是的,我还没有看到完整的画面。

【问题讨论】:

  • 我觉得有点内疚,将此标记为“需要更多关注”的问题,因为我理解你的那种不堪重负的感觉。但请注意,Stack Overflow 上还有其他“我如何编写转译器”问题,这些问题在结果方面也表现不佳。所以我认为你不会得到你想要的答案。 Babel 是开源的,从那里开始。我只是认为这个问题太大了,其中不大的部分无异于图书馆推荐,正如马特的回答开始显示的那样。

标签: javascript algorithm parsing abstract-syntax-tree transpiler


【解决方案1】:

编写转译器是一项非常艰巨的工作……但是,由于各种原因,JavaScript 工作流程已经充满了转译器,因此有很多工具可以提供帮助。

如果您的目标语言看起来像 JavaScript,那么您可以将转译器编写为 Babel 的插件:https://babeljs.io/

否则,可能以jscodeshift 开头,这将为您提供易于访问的 AST。

许多开源 javascript 工具,例如 eslint,也有 javscript 解析器,您可以通过一些努力来提取。

另见AST Explorer

一旦你有了一个 AST,你通常会递归地处理它,可能会遵循访问者模式,以将每个 AST 节点转换为等效的目标结构。然后可能会优化窥视孔以简化生成的 AST。然后最后序列化它。 jscodeshift 带有一个 javascript 序列化器,你可以用你自己的替换它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-15
    • 1970-01-01
    • 2014-11-04
    • 2011-01-04
    • 1970-01-01
    • 2015-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多