当前位置: 网站首页>小程序开发>网站制作

昌邑400电话办理【昌邑网站公司】昌邑百度优化、昌邑域名注册、昌邑网店美工、昌邑微信公众号托管

发表日期: 2021-05-20 14:04:52 浏览次数:76

昌邑400电话办理【昌邑网站公司】昌邑百度优化、昌邑域名注册、昌邑网店美工、昌邑微信公众号托管

网站建设.jpg

昌邑市属于潍坊市下辖的县级市,位于山东半岛西北部,潍河下游,莱州湾畔,介于北纬36°25′-37°08′,东经119°13′-119°37′之间,东与莱州市、平度市以胶莱河为界,南与高密市、安丘市毗连,西与潍坊市坊子区、寒亭区为邻,北濒莱州湾,属市域总面积1578.7平方公里。 [1-2]  截至2016年,昌邑市辖3个街道、6个镇。

昌邑市属环渤海经济圈,为国务院确定的沿海对外开放城市之一,被誉为中国丝绸之乡、华侨之乡和中国溴·盐之乡, 先后被评为中国北方绿化苗木基地、中国纺织产业基地市、中国超纤产业基地、中国中小城市综合实力百强县市 [3]  、中国最具投资潜力中小城市百强县市 [4]  等荣誉称号。 [5]  2018年10月,昌邑市入选“综合实力百强县”、 [6]  全国投资潜力百强县市、 [7]  全国绿色发展百强县市 [8]  、全国科技创新百强县市、全国新型城镇化质量百强县市 [9]  。 [10]  2019年10月8日,被评为2019年度全国综合实力百强县市、2019年度全国绿色发展百强县市、全国科技创新百强县市 [11]  。 [12-13]  2020年山东省四星级新型智慧城市建设预试点城市, [14]  全国文明城市。 [15] 


语义分析

语义分析就是把词汇进行立体的组合,确定有多重意义的词语最终是什么意思、多个词语之间有什么关系以及又应该再哪里断句等。

在编程语言解释当中,这就是要最终生成语法树的步骤了。不像自然语言,像“从句”这种结构往往最多只有一层,编程语言的各种从属关系更加复杂。

在编程语言的解析中有两个很相似但是又有区别的重要概念:

  • 语句:语句是一个具备边界的代码区域,相邻的两个语句之间从语法上来讲互不干扰,调换顺序虽然可能会影响执行结果,但不会产生语法错误
    比如return true、var a = 10、if (…) {…}
  • 表达式:最终有个结果的一小段代码,它的特点是可以原样嵌入到另一个表达式
    比如myVar、1+1、str.replace(‘a’, ‘b’)、i < 10 && i > 0等

很多情况下一个语句可能只包含一个表达式,比如console.log(‘hi’);。estree标准当中,这种语句节点称作ExpressionStatement。

语义分析的过程又是个遍历语法单元的过程,不过相比较而言更复杂,因为分词过程中,每个语法单元都是独立平铺的,而语法分析中,语句和表达式会以树状的结构互相包含。针对这种情况我们可以用栈,也可以用递归来实现。

我继续上面的例子给出语义分析的代码,代码很长,先在最开头说明几个函数是做什么的:

  • nextStatement:读取并返回下一个语句
  • nextExpression:读取并返回下一个表达式
  • nextToken:读取下一个语法单元(或称符号),赋值给curToken
  • stash:暂存当前读取符号的位置,方便在需要的时候返回
  • rewind:返回到上一个暂存点
  • commit:上一个暂存点不再被需要,将其销毁

这里stash、rewind、commit都跟读取位置暂存相关,什么样的情况会需要返回到暂存点呢?有时同一种语法单元有可能代表不同类型的表达式的开始。先stash,然后按照其中一种尝试解析,如果解析成功了,那么暂存点就没用了,commit将其销毁。如果解析失败了,就用rewind回到原来的位置再按照另一种方式尝试去解析。

以下是代码:

function parse (tokens) {
  let i = -1;     // 用于标识当前遍历位置
  let curToken;   // 用于记录当前符号

  // 读取下一个语句
  function nextStatement () {
    // 暂存当前的i,如果无法找到符合条件的情况会需要回到这里
    stash();
    
    // 读取下一个符号
    nextToken();

    if (curToken.type === 'identifier' && curToken.value === 'if') {
      // 解析 if 语句
      const statement = {
        type: 'IfStatement',
      };
      // if 后面必须紧跟着 (
      nextToken();
      if (curToken.type !== 'parens' || curToken.value !== '(') {
        throw new Error('Expected ( after if');
      }

      // 后续的一个表达式是 if 的判断条件
      statement.test = nextExpression();

      // 判断条件之后必须是 )
      nextToken();
      if (curToken.type !== 'parens' || curToken.value !== ')') {
        throw new Error('Expected ) after if test expression');
      }

      // 下一个语句是 if 成立时执行的语句
      statement.consequent = nextStatement();

      // 如果下一个符号是 else 就说明还存在 if 不成立时的逻辑
      if (curToken === 'identifier' && curToken.value === 'else') {
        statement.alternative = nextStatement();
      } else {
        statement.alternative = null;
      }
      commit();
      return statement;
    }

    if (curToken.type === 'brace' && curToken.value === '{') {
      // 以 { 开头表示是个代码块,我们暂不考虑JSON语法的存在
      const statement = {
        type: 'BlockStatement',
        body: [],
      };
      while (i < tokens.length) {
        // 检查下一个符号是不是 }
        stash();
        nextToken();
        if (curToken.type === 'brace' && curToken.value === '}') {
          // } 表示代码块的结尾
          commit();
          break;
        }
        // 还原到原来的位置,并将解析的下一个语句加到body
        rewind();
        statement.body.push(nextStatement());
      }
      // 代码块语句解析完毕,返回结果
      commit();
      return statement;
    }
    
    // 没有找到特别的语句标志,回到语句开头
    rewind();

    // 尝试解析单表达式语句
    const statement = {
      type: 'ExpressionStatement',
      expression: nextExpression(),
    };
    if (statement.expression) {
      nextToken();
      if (curToken.type !== 'EOF' && curToken.type !== 'sep') {
        throw new Error('Missing ; at end of expression');
      }
      return statement;
    }
  }

  // 读取下一个表达式
  function nextExpression () {
    nextToken();

    if (curToken.type === 'identifier') {
      const identifier = {
        type: 'Identifier',
        name: curToken.value,
      };
      stash();
      nextToken();
      if (curToken.type === 'parens' && curToken.value === '(') {
        // 如果一个标识符后面紧跟着 ( ,说明是个函数调用表达式
        const expr = {
          type: 'CallExpression',
          caller: identifier,
          arguments: [],
        };

        stash();
        nextToken();
        if (curToken.type === 'parens' && curToken.value === ')') {
          // 如果下一个符合直接就是 ) ,说明没有参数
          commit();
        } else {
          // 读取函数调用参数
          rewind();
          while (i < tokens.length) {
            // 将下一个表达式加到arguments当中
            expr.arguments.push(nextExpression());
            nextToken();
            // 遇到 ) 结束
            if (curToken.type === 'parens' && curToken.value === ')') {
              break;
            }
            // 参数间必须以 , 相间隔
            if (curToken.type !== 'comma' && curToken.value !== ',') {
              throw new Error('Expected , between arguments');
            }
          }
        }
        commit();
        return expr;
      }
      rewind();
      return identifier;
    }

    if (curToken.type === 'number' || curToken.type === 'string') {
      // 数字或字符串,说明此处是个常量表达式
      const literal = {
        type: 'Literal',
        value: eval(curToken.value),
      };
      // 但如果下一个符号是运算符,那么这就是个双元运算表达式
      // 此处暂不考虑多个运算衔接,或者有变量存在
      stash();
      nextToken();
      if (curToken.type === 'operator') {
        commit();
        return {
          type: 'BinaryExpression',
          left: literal,
          right: nextExpression(),
        };
      }
      rewind();
      return literal;
    }

    if (curToken.type !== 'EOF') {
      throw new Error('Unexpected token ' + curToken.value);
    }
  }

  // 往后移动读取指针,自动跳过空白
  function nextToken () {
    do {
      i++;
      curToken = tokens[i] || { type: 'EOF' };
    } while (curToken.type === 'whitespace');
  }

  // 位置暂存栈,用于支持很多时候需要返回到某个之前的位置
  const stashStack = [];

  function stash (cb) {
    // 暂存当前位置
    stashStack.push(i);
  }

  function rewind () {
    // 解析失败,回到上一个暂存的位置
    i = stashStack.pop();
    curToken = tokens[i];
  }

  function commit () {
    // 解析成功,不需要再返回
    stashStack.pop();
  }
  
  const ast = {
    type: 'Program',
    body: [],
  };

  // 逐条解析顶层语句
  while (i < tokens.length) {
    const statement = nextStatement();
    if (!statement) {
      break;
    }
    ast.body.push(statement);
  }
  return ast;
}

const ast = parse([
 { type: "whitespace", value: "\n" },
 { type: "identifier", value: "if" },
 { type: "whitespace", value: " " },
 { type: "parens", value: "(" },
 { type: "number", value: "1" },
 { type: "whitespace", value: " " },
 { type: "operator", value: ">" },
 { type: "whitespace", value: " " },
 { type: "number", value: "0" },
 { type: "parens", value: ")" },
 { type: "whitespace", value: " " },
 { type: "brace", value: "{" },
 { type: "whitespace", value: "\n " },
 { type: "identifier", value: "alert" },
 { type: "parens", value: "(" },
 { type: "string", value: "\"if 1 > 0\"" },
 { type: "parens", value: ")" },
 { type: "sep", value: ";" },
 { type: "whitespace", value: "\n" },
 { type: "brace", value: "}" },
 { type: "whitespace", value: "\n" },
]);

最终得到结果:

{
  "type": "Program",
  "body": [
    {
      "type": "IfStatement",
      "test": {
        "type": "BinaryExpression",
        "left": {
          "type": "Literal",
          "value": 1
        },
        "right": {
          "type": "Literal",
          "value": 0
        }
      },
      "consequent": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ExpressionStatement",
            "expression": {
              "type": "CallExpression",
              "caller": {
                "type": "Identifier",
                "value": "alert"
              },
              "arguments": [
                {
                  "type": "Literal",
                  "value": "if 1 > 0"
                }
              ]
            }
          }
        ]
      },
      "alternative": null
    }
  ]
}

以上就是语义解析的部分主要思路。注意现在的nextExpression已经颇为复杂,但实际实现要比现在这里展示的要更复杂很多,因为这里根本没有考虑单元运算符、运算优先级等等。

结语

真正看下来,其实没有哪个地方的原理特别高深莫测,就是精细活,需要考虑到各种各样的情况。总之要做一个完整的语法解释器需要的是十分的细心与耐心。

在并不是特别远的过去,做web项目,前端技术都还很简单,甚至那时候的网页都尽量不用JavaScript。之后jQuery的诞生真正地让JS成为了web应用开发核心,web前端工程师这种职业也才真正独立出来。但后来随着语言预处理和打包等技术的出现,前端真的是越来越强大但是技术栈也真的是变得越来越复杂。虽然有种永远都学不完的感觉,但这更能体现出我们前端工程存在的价值,不是吗?


昌邑400电话办理昌邑网站公司昌邑百度优化、昌邑域名注册、昌邑网店美工、昌邑微信公众号托管

400-111-6878
服务热线
顶部

备案号: 苏ICP备11067224号

CopyRight © 2011 书生商友信息科技 All Right Reserved

24小时服务热线:400-111-6878   E-MAIL:1120768800@qq.com   QQ:1120768800

  网址: http://www.768800.com  网站建设上往建站

关键词: 网站建设| 域名邮箱| 服务器空间| 网站推广| 上往建站| 网站制作| 网站设计| 域名注册| 网络营销| 网站维护|

企业邮箱| 虚拟主机| 网络建站| 网站服务| 网页设计| 网店美工设计| 网站定制| 企业建站| 网站设计制作| 网页制作公司|

400电话办理| 书生商友软件| 葬花网| 调温纤维| 海洋馆运营维护| 北京保安公司| 殡仪馆服务| 殡葬服务| 苏州殡葬一条龙| 朝阳殡葬| 苏州殡葬服务|

预约专家

欢迎您免费咨询,请填写以下信息,我们收到后会尽快与您联系

  

服务热线:400-111-6878