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

代码是如何被被编译的?

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

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

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

思考

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

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

接下来,我们需要做的是将上面的文本转换成下面的存储结构,也就是AST(抽象语法树):pcf28资讯网——每日最新资讯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的操作是非常方便的:pcf28资讯网——每日最新资讯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内容添加样式,并为不同类型的元素绑定事件与处理逻辑,这样不仅可以对输入内容高亮,在点击函数、单元格时,还可以给出对应的提示信息或响应。pcf28资讯网——每日最新资讯28at.com

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

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

实现

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

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

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

下面是具体的实现过程:pcf28资讯网——每日最新资讯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这样的框架,其编译过程也是基于这个思路,通过一系列的处理流程,最终生成带有语言特性的内容。pcf28资讯网——每日最新资讯28at.com

结束语

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

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

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

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

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

标签:
  • 热门焦点
  • 一加Ace2 Pro官宣:普及16G内存 引领24G

    一加官方今天继续为本月发布的新机一加Ace2 Pro带来预热,公布了内存方面的信息。“淘汰 8GB ,12GB 起步,16GB 普及,24GB 引领,还有呢?#一加Ace2Pro#,2023 年 8 月,敬请期待。”同时
  • Mate60手机壳曝光 致敬自己的经典设计

    8月3日消息,今天下午博主数码闲聊站带来了华为Mate60的第三方手机壳图,可以让我们在真机发布之前看看这款华为全新旗舰的大致轮廓。从曝光的图片看,Mate 60背后摄像头面积依然
  • 中兴AX5400Pro+上手体验:再升级 双2.5G网口+USB 3.0这次全都有

    2021年11月的时候,中兴先后发布了两款路由器产品,中兴AX5400和中兴AX5400 Pro,从产品命名上就不难看出这是隶属于同一系列的,但在外观设计上这两款产品可以说是完全没一点关系
  • 6月安卓手机性价比榜:Note 12 Turbo断层式碾压

    6月份有一个618,虽然这是京东周年庆的日子,但别的电商也都不约而同的跟进了,反正促销没坏处,厂商和用户都能满意。618期间一些产品也出现了历史低价,那么各个价位段的产品性价比
  • 十个可以手动编写的 JavaScript 数组 API

    JavaScript 中有很多API,使用得当,会很方便,省力不少。 你知道它的原理吗? 今天这篇文章,我们将对它们进行一次小总结。现在开始吧。1.forEach()forEach()用于遍历数组接收一参
  • 一文看懂为苹果Vision Pro开发应用程序

    译者 | 布加迪审校 | 重楼苹果的Vision Pro是一款混合现实(MR)头戴设备。Vision Pro结合了虚拟现实(VR)和增强现实(AR)的沉浸感。其高分辨率显示屏、先进的传感器和强大的处理能力
  • 虚拟键盘 API 的妙用

    你是否在遇到过这样的问题:移动设备上有一个固定元素,当激活虚拟键盘时,该元素被隐藏在了键盘下方?多年来,这一直是 Web 上的默认行为,在本文中,我们将探讨这个问题、为什么会发生
  • 一文掌握 Golang 模糊测试(Fuzz Testing)

    模糊测试(Fuzz Testing)模糊测试(Fuzz Testing)是通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。可以用来发现应用程序、操作系统和网络协议等中的漏洞或
  • 自律,给不了Keep自由!

    来源 | 互联网品牌官作者 | 李大为编排 | 又耳 审核 | 谷晓辉自律能不能给用户自由暂时不好说,但大概率不能给Keep自由。近日,全球最大的在线健身平台Keep正式登陆港交所,努力
Top