javascript - 将转译后的代码映射回原始标记脚本

标签 javascript algorithm transpiler

最近有人问是否有如下简单的方法来转换自定义标记,包括嵌套标记。示例包括...

  • \k[hello]输出将是 <b>hello</b>
  • \i[world] ,输出将为 <em>world</em>
  • hello \k[dear \i[world]] ,输出将为 hello <b>dear <em>world</em></b>
  • \b[some text](url) ,输出将为 <a href=”url”>some text</a>
  • \r[some text](url) ,输出将为 <img alt=”some text” src=”url” />

有趣的是,将上述代码转换为 javascript(包括考虑嵌套)非常简单,尤其是在标记语法一致的情况下。

//
// Define the syntax and translation to javascript.
//
const grammar = {

  syntax: {
    k:      {markUp: `\k[`, javascript: `"+grammar.oneArg("k","`,  pre: `<b>`,  post: `</b>`},
    i:      {markUp: `\i[`, javascript: `"+grammar.oneArg("i","`,  pre: `<em>`, post: `</em>`},
    b:      {markUp: `\b[`, javascript: `"+grammar.twoArgs("b","`, pattern: `<a href="$2">$1</a>`},
    r:      {markUp: `\r[`, javascript: `"+grammar.twoArgs("r","`, pattern: `<img alt="$1" src="$2"/>`},
    close0: {markUp: `](`,   javascript: `","`},
    close1: {markUp: `)`,    javascript: `")+"`},
    close2: {markUp: `]`,    javascript: `")+"`}
  },

  oneArg: function( command, arg1 ) {
    return grammar.syntax[ command ].pre + arg1 + grammar.syntax[ command ].post;
  },

  twoArgs: function( command, arg1, arg2 ) {
    return grammar.syntax[ command ].pattern.split( `$1` ).join( arg1 ).split( `$2` ).join( arg2 );
  }
}


function transpileAndExecute( markUpString ) {
  // Convert the markUp to javascript.
  for ( command in grammar.syntax ) {
    markUpString = markUpString.split( grammar.syntax[ command ].markUp ).join( grammar.syntax[ command ].javascript );
  }

  // With the markUp now converted to javascript, let's execute it!
  return new Function( `return "${markUpString}"` )();
}

var markUpTest = `Hello \k[dear \i[world!]] \b[\i[Search:] \k[Engine 1]](http://www.google.com) \r[\i[Search:] \k[Engine 2]](http://www.yahoo.com)`;

console.log( transpileAndExecute( markUpTest ) );

请注意,显然还有一些预处理问题也必须解决,例如如何处理普通文本中包含的标记。例如,将“]”作为文本字符串的一部分包含在转译器中会抛出一个曲线球,因此强制执行一个规则,例如使用“\]”来表示“]”,然后替换所有此类出现的“\]”在转译之前使用无害的文本然后在之后重新替换可以简单地解决这个问题......

在转译方面,使用上面定义的语法,下面的标记...

Hello \k[dear \i[world!]] \b[\i[Search:] \k[Engine 1]](http://www.google.com) \r[\i[Search:] \k[Engine 2]](http://www.yahoo.com)

...被转译为...

"Hello world! "+grammar.oneArg("k","dear "+grammar.oneArg("i","world")+"")+" "+grammar.twoArgs("b",""+grammar.oneArg("i","Search:")+" "+grammar.oneArg("k","Engine 1")+"","http://www.google.com")+" "+grammar.twoArgs("r",""+grammar.oneArg("i","Search:")+" "+grammar.oneArg("k","Engine 2")+"","http://www.yahoo.com")+""

...一旦作为 javascript 函数执行,结果...

Hello <b>dear <em>world!</em></b> <a href="http://www.google.com"><em>Search:</em> <b>Engine 1</b></a> <img alt="<em>Search:</em> <b>Engine 2</b>" src="http://www.yahoo.com"/>

但真正的挑战是语法错误的处理,尤其是当有大量标记需要转译时。 CertainPerformance 的清晰答案(参见 Find details of SyntaxError thrown by javascript new Function() constructor)提供了一种从动态编译的 javascript 函数中捕获语法错误的行号和字符号的方法,但我不太确定映射语法错误的最佳方法将代码转译回原始标记。

例如,如果一个额外的 ']' 不合适(在“再见”之后)...

Hello World! \b[\i[Goodbye]]] \k[World!]]

...这转换为...

"Hello World! "+grammar.twoArgs("b",""+grammar.oneArg("i","Goodbye")+"")+"")+" "+grammar.oneArg("k","World!")+"")+""
                                                                           ^

...并且 CertainPerformance 的 checkSyntax 函数返回“错误抛出时间:1:76”,正如预期的那样,上面用“^”标记。

问题是,如何将其映射回原始标记以帮助缩小标记中的错误? (显然,在这种情况下,很容易看到标记中的错误,但如果有标记页面被转译,则必须协助缩小语法错误的范围。)维护标记和转译代码之间的映射似乎棘手的是,当转译器遍历语法转换矩阵时,它会逐步将标记变异为 javascript 代码。我的直觉告诉我有一个更简单的方法...感谢您的关注。

最佳答案

我建议您编写一个语法检查器,有点像 jsonlint 或 jslint 等...在实际将文本编译为人类可读的文本之前检查是否正确检查和关闭了所有内容。

这允许调试,并防止格式错误的代码乱七八糟地运行,并允许您在编辑文本时提供错误突出显示的文档编辑器。

下面是一个概念证明,它只检查括号是否正确闭合。

var grammarLint = function(text) {
  var nestingCounter = 0;
  var isCommand = char => char == '\\';
  var isOpen = char => char == '[';
  var isClose = char => char == ']';
  var lines = text.split('\n');
  for(var i = 0; i < lines.length; i++) {
    text = lines[i];
    for(var c = 0; c < text.length; c++) {
     var char = text.charAt(c);
     if(isCommand(char) && isOpen(text.charAt(c+2))) {
        c += 2;
        nestingCounter++;
        continue;
     }
     if(isClose(char)) {
        nestingCounter--;
        if(nestingCounter < 0) {
            throw new Error('Command closed but not opened at on line '+(i+1)+' char '+(c+1));
        }
      }
    }
  }
  if(nestingCounter > 0) {
     throw new Error(nestingCounter + ' Unclosed command brackets found');
  }
}
text = 'Hello World! \\b[\\i[Goodbye]]] \\k[World!]]';
try {
   grammarLint(text);
}
catch(e) {
   console.error(e.message);
}
text = 'Hello World! \\b[\\i[Goodbye \\k[World!]]';
try {
   grammarLint(text);
}
catch(e) {
   console.error(e.message);
}

关于javascript - 将转译后的代码映射回原始标记脚本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56572266/

相关文章:

algorithm - 作业 : Implementing Karp-Rabin; For the hash values modulo q, 解释为什么用 q 作为 2 的幂是个坏主意?

ios - 模块 '"node_modules/@angular/animations/animations "' has no exported member ' AnimationBuilder'

javascript - 将 Fortran 语言转换为 Javascript

javascript - 如何使用一个输出分别汇总多个目录

javascript - Jquery在quote中设置引号

javascript - 为什么输出给出 'undefined' 而不是 JS 示例中的 'number'?

javascript - 如何通过 id 查找属于两个类中任一类的子元素?

javascript - polymer JS : Iron-Ajax - How to Bind Token to Headers Property?

algorithm - 最大流边约束

Python合并算法