将一种语言转换为另一种语言的一般方法是什么?
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 转换为目标语言(在本例中为 LinkScript)的 AST。然后将输出的 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
所以赋值表达式有点颠倒了。在其他情况下会变得更加复杂。
所以就好像我需要研究每种类型的映射,并就如何进行映射提出详细的计划或算法。那么似乎我需要研究成千上万种可能的 transformation/mapping 类型。所以从这个意义上说,这似乎是一个非常耗时的问题,需要精神上的解决。
有没有更简单的方法?
很长一段时间(几年?)我一直想这样做,但就像我暗示的那样,这似乎总是一项极其艰巨的任务。我认为这是因为我没有清楚地看到所有不同的东西 ways/angles 我可以收到 AST,而且我不知道如何将它归结为我可以看到的东西。
除了弄清楚如何执行每种类型的 mapping/transformation 之外,我还应该有一些可以扩展的不错的代码。这通常是我的强项(用简单的 API 编写干净的代码),但在这里我很挣扎,因为是的,我还没有看到完整的画面。
编写转译器是一项非常艰巨的工作...但是,由于各种原因,JavaScript 工作流程中已经充满了转译器,因此有很多工具可以提供帮助。
如果您的目标语言看起来像 JavaScript,那么您可以将您的转译器编写为 Babel 的插件:https://babeljs.io/
否则,也许从 jscodeshift 开始,这将为您提供易于访问的 AST。
许多开源 javascript 工具,例如 eslint,也有 javscript 解析器,您可以稍加努力就可以将其提取出来。
另见 AST Explorer
一旦你有了 AST,你通常会递归地处理它,可能遵循访问者模式,将每个 AST 节点转换成等效的目标结构。然后可能优化窥孔以简化生成的 AST。然后最后序列化它。 jscodeshift 带有一个 javascript 序列化器,您可以用自己的序列化器替换它。
我想将 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 转换为目标语言(在本例中为 LinkScript)的 AST。然后将输出的 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
所以赋值表达式有点颠倒了。在其他情况下会变得更加复杂。
所以就好像我需要研究每种类型的映射,并就如何进行映射提出详细的计划或算法。那么似乎我需要研究成千上万种可能的 transformation/mapping 类型。所以从这个意义上说,这似乎是一个非常耗时的问题,需要精神上的解决。
有没有更简单的方法?
很长一段时间(几年?)我一直想这样做,但就像我暗示的那样,这似乎总是一项极其艰巨的任务。我认为这是因为我没有清楚地看到所有不同的东西 ways/angles 我可以收到 AST,而且我不知道如何将它归结为我可以看到的东西。
除了弄清楚如何执行每种类型的 mapping/transformation 之外,我还应该有一些可以扩展的不错的代码。这通常是我的强项(用简单的 API 编写干净的代码),但在这里我很挣扎,因为是的,我还没有看到完整的画面。
编写转译器是一项非常艰巨的工作...但是,由于各种原因,JavaScript 工作流程中已经充满了转译器,因此有很多工具可以提供帮助。
如果您的目标语言看起来像 JavaScript,那么您可以将您的转译器编写为 Babel 的插件:https://babeljs.io/
否则,也许从 jscodeshift 开始,这将为您提供易于访问的 AST。
许多开源 javascript 工具,例如 eslint,也有 javscript 解析器,您可以稍加努力就可以将其提取出来。
另见 AST Explorer
一旦你有了 AST,你通常会递归地处理它,可能遵循访问者模式,将每个 AST 节点转换成等效的目标结构。然后可能优化窥孔以简化生成的 AST。然后最后序列化它。 jscodeshift 带有一个 javascript 序列化器,您可以用自己的序列化器替换它。