当前位置:首页 > 科技  > 软件

代码是如何被被编译的?

来源: 责编: 时间:2024-04-02 17:23:08 107观看
导读最近需要写一个编辑扩展组件,主要功能类似于Excel的单元格编辑框,主要针对单元格输入内容的处理。要知道在Excel中,每个单元格除了可以输入文本内容(包括字符、数字、日期等)外,还有包括函数。 那么在输入函数时,如果聚焦到

最近需要写一个编辑扩展组件,主要功能类似于Excel的单元格编辑框,主要针对单元格输入内容的处理。要知道在Excel中,每个单元格除了可以输入文本内容(包括字符、数字、日期等)外,还有包括函数。 那么在输入函数时,如果聚焦到函数(比如IF VLOOKUP)或单元格(比如A1 A2)上,都会有对应的响应,那么我们输入的文本,是如何识别到其中的函数,常量,单元格的呢?并且在被聚焦后能够做出对应的响应?P7K28资讯网——每日最新资讯28at.com

基于这样的需求,经过分析后发现真正需要做的事情,其实是将输入的内容进行解析,然后根据解析的结果,进行相应的处理,最终生成一段HTML内容,而所有的点击响应都是对DOM元素的事件监听而已, 而这里面的关键则是如何将输入的内容,解析成对应的AST,后面生成HTML的过程也就简单了。P7K28资讯网——每日最新资讯28at.com

思考

现在有这样的一个Excel公式,调用了IF、VLOOKUP函数,引用了单元格与外部sheet区域,包括操作符号,常量等等元素:P7K28资讯网——每日最新资讯28at.com

IF(VLOOKUP(A1,"sheet1"!$A$1:$C$50,1,2) > 3, 4, 5)

接下来,我们需要做的是将上面的文本转换成下面的存储结构,也就是AST(抽象语法树):P7K28资讯网——每日最新资讯28at.com

{  type: 'Program',  body: [{ type: 'function', name: 'IF', params: [        {            type: 'expression,            body: [                {                    type: 'function',                    name: 'VLOOKUP',                    params: [                        {                            type: 'cell',                            value: 'A1',                        },                         {                            type: 'range',                            ref: 'sheet1'                            value: '$A$1:$C$50',                        },                        {                            type: 'number',                            value: '1'                        },                        {                            type: 'number',                            value: '2'                        }                    ]                },                {                    type: 'operator',                    value: '>'                },                {                    type: 'number',                    value: '3'                }            ]        },        {            type: 'number',            value: '4',        },         {            type: 'NumberLiteral',            value: '5',        }    ]  }]}

基于结构化的树,可以很放标转换成类似下面的HTML形式,要知道对html的操作是非常方便的:P7K28资讯网——每日最新资讯28at.com

<div class="editor">    <span class="function">IF</span>    <span>(</span>    <span class="exception">        <span>VLOOKUP</span>        <span>(</span>        <span class="cell">A1</span>        <span>,</span>        <span class="ref">sheet1!</span>        <span class="range">"sheet"!$A$1:$C$50</span>        <span>,</span>        <span class="number">1</span>        <span>,</span>        <span class="number">2</span>        <span>)</span>        <span class="operator">></span>        <span class="number">3</span>    </span>    <span class="number">4</span>    <span>,</span>    <span class="number">5</span>    <span>)</span></div>

最后为为上面的html内容添加样式,并为不同类型的元素绑定事件与处理逻辑,这样不仅可以对输入内容高亮,在点击函数、单元格时,还可以给出对应的提示信息或响应。P7K28资讯网——每日最新资讯28at.com

上面的内容仅仅是实现一个功能的思考与假设,实际的实现可能有所差异,但是实现的过程是类似的。P7K28资讯网——每日最新资讯28at.com

在我们使用的所有编程语言,比如Java、C、javascript等,我们都会编写文本格式的源代码,编译器或解释器会将源代码按照语言语法解析成对应的语法结构树,基于该结构一来可以实现语法检查、代码高亮等功能,同时针对特定代码块可以有很多其他操作。P7K28资讯网——每日最新资讯28at.com

实现

通过上面的分析,可以看到核心要实现的是将文本内容解析成语法树的过程。在完成这部分功能的过程中,看到一个基于javascript实现的类似功能的例子the-super-tiny-compiler.js,通过几百行的代码,把这这一过程清晰地表现出来。P7K28资讯网——每日最新资讯28at.com

其过程主要包括下面几个步骤:P7K28资讯网——每日最新资讯28at.com

  1. tokenizer:词法分析的,以字符为单位逐个遍历文本,构建成一个token数组来描述文本内容,其中每一个token主要包含type,value
  2. parser:语法分析,将上面的token数组转换树结构
  3. transformer:遍历上面语法树各种类型的节点,针对不同的节点类型,执行不同的操作,生成最终带有语言特性标识的语法树
  4. codeGenerator:基于语法树生成最终的代码

下面是具体的实现过程:P7K28资讯网——每日最新资讯28at.com

tokenizer

function tokenizer(input) {  let current = 0;  let tokens = [];  while (current < input.length) {    let char = input[current];    if (char === '(') {      tokens.push({        type: 'paren',        value: '(',      });      current++;      continue;    }    if (char === ')') {      tokens.push({        type: 'paren',        value: ')',      });      current++;      continue;    }    let WHITESPACE = //s/;    if (WHITESPACE.test(char)) {      current++;      continue;    }    let NUMBERS = /[0-9]/;    if (NUMBERS.test(char)) {      let value = '';      while (NUMBERS.test(char)) {        value += char;        char = input[++current];      }      tokens.push({ type: 'number', value });      continue;    }    if (char === '"') {      let value = '';      char = input[++current];      while (char !== '"') {        value += char;        char = input[++current];      }      char = input[++current];      tokens.push({ type: 'string', value });      continue;    }    let LETTERS = /[a-z]/i;    if (LETTERS.test(char)) {      let value = '';      while (LETTERS.test(char)) {        value += char;        char = input[++current];      }      tokens.push({ type: 'name', value });      continue;    }    throw new TypeError('I dont know what this character is: ' + char);  }  return tokens;}

parser

function parser(tokens) {  let current = 0;  function walk() {    let token = tokens[current];    if (token.type === 'number') {      current++;      return {        type: 'NumberLiteral',        value: token.value,      };    }    if (token.type === 'string') {      current++;      return {        type: 'StringLiteral',        value: token.value,      };    }    if (      token.type === 'paren' &&      token.value === '('    ) {      token = tokens[++current];      let node = {        type: 'CallExpression',        name: token.value,        params: [],      };      token = tokens[++current];      while (        (token.type !== 'paren') ||        (token.type === 'paren' && token.value !== ')')      ) {        node.params.push(walk());        token = tokens[current];      }      current++;      return node;    }    throw new TypeError(token.type);  }  let ast = {    type: 'Program',    body: [],  };  while (current < tokens.length) {    ast.body.push(walk());  }  return ast;}

traverser

function traverser(ast, visitor) {  function traverseArray(array, parent) {    array.forEach(child => {      traverseNode(child, parent);    });  }  function traverseNode(node, parent) {    let methods = visitor[node.type];    if (methods && methods.enter) {      methods.enter(node, parent);    }    switch (node.type) {      case 'Program':        traverseArray(node.body, node);        break;      case 'CallExpression':        traverseArray(node.params, node);        break;      case 'NumberLiteral':      case 'StringLiteral':        break;      default:        throw new TypeError(node.type);    }    if (methods && methods.exit) {      methods.exit(node, parent);    }  }  traverseNode(ast, null);}

transformer

function transformer(ast) {  let newAst = {    type: 'Program',    body: [],  };  ast._context = newAst.body;  traverser(ast, {    NumberLiteral: {      enter(node, parent) {        parent._context.push({          type: 'NumberLiteral',          value: node.value,        });      },    },    StringLiteral: {      enter(node, parent) {        parent._context.push({          type: 'StringLiteral',          value: node.value,        });      },    },    CallExpression: {      enter(node, parent) {        let expression = {          type: 'CallExpression',          callee: {            type: 'Identifier',            name: node.name,          },          arguments: [],        };        node._context = expression.arguments;        if (parent.type !== 'CallExpression') {          expression = {            type: 'ExpressionStatement',            expression: expression,          };        }        parent._context.push(expression);      },    }  });  return newAst;}

codeGenerator

function codeGenerator(node) {  switch (node.type) {    case 'Program':      return node.body.map(codeGenerator)        .join('/n');    case 'ExpressionStatement':      return (        codeGenerator(node.expression) +      );    case 'CallExpression':      return (        codeGenerator(node.callee) +        '(' +        node.arguments.map(codeGenerator)          .join(', ') +        ')'      );    case 'Identifier':      return node.name;    case 'NumberLiteral':      return node.value;    case 'StringLiteral':      return '"' + node.value + '"';    default:      throw new TypeError(node.type);  }}

compiler

function compiler(input) {  let tokens = tokenizer(input);  let ast    = parser(tokens);  let newAst = transformer(ast);  let output = codeGenerator(newAst);  return output;}
const input  = '(add 2 (subtract 4 2))';compiler(input);

感兴趣的可以查看相关的源代码,上面的解释比代码还多,逐行都有非常详细的描述。虽然只是一段简单的示例,但是将一段代码的编译过程都清晰地展现出来,对理解编译过程非常有帮助。像Vue React这样的框架,其编译过程也是基于这个思路,通过一系列的处理流程,最终生成带有语言特性的内容。P7K28资讯网——每日最新资讯28at.com

结束语

本篇文章主要基于目前遇到的的一个需求,结合自己分析与思考来了解需求的本质,最后通过一个javascript的示例,帮助我们理解这个过程。P7K28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-80883-0.html代码是如何被被编译的?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 你们单测覆盖率是如何统计的?原理是什么?

下一篇: 如何仅使用CSS创建一个环形进度条?

标签:
  • 热门焦点
Top