javascript 원 순환 값 계산기 만 드 는 방법

이전 글 에서 우 리 는 AST 를 통 해 위 챗 애플 릿 구성 요소 의 다 중 컴 파일 을 완 성 했 습 니 다.이 글 에서 우 리 는 좀 더 깊이 있 게 AST 를 통 해 자바 script 원 순환 값 구 하 기 를 완성 합 니 다.
구조
원 순환 값 계산기
  • tokenizer:코드 텍스트 에 대해 품사 와 문법 분석 을 하고 코드 를 여러 개의 token
  • 으로 나 누 었 다.
  • parser:token 에 따라 AST 트 리 생 성
  • evauite:AST 트 리 노드 의 type 에 따라 대응 하 는 apply 방법 을 실행 합 니 다
  • apply:환경 에 따라 실제 수치 계산
  • 을 실시한다.
  • scope:현재 코드 가 실 행 된 환경
  • 코드 디 렉 터 리
    구조 에 따라 나 는 코드 디 렉 터 리 를 크게 다음 과 같은 몇 개의 파일 로 나 누 었 다.
  • parser
  • eval
  • scope
  • tokenizer 와 parser 두 과정 은 본 고의 중점 이 아 닙 니 다.저 는 parser 에 통일 적 으로 두 었 고@babel/parser 에 맡 겼 습 니 다.
    evaluate 와 apply 두 과정 을 eval 파일 에 통일 적 으로 처리 하 였 습 니 다.잠시 후에 우 리 는 이 부분 을 중점적으로 보 겠 습 니 다.
    scope 는 scope 파일 에 넣 습 니 다.
    evaluate-apply
    이것 은 사실 재 귀적 으로 계산 하 는 과정 이다.
    우선,evaluate 는 두 개의 인 자 를 받 습 니 다.node 가 현재 옮 겨 다 니 는 AST 트 리 노드 와 scope 현재 환경 입 니 다.그리고 evaluate 는 node 의 type 속성 에 따라 이 노드 가 어떤 유형 인지 판단 합 니 다.유형 을 판단 한 후 apply 를 실행 하여 이 노드 가 대표 하 는 표현 식 을 구 합 니 다.apply 에서 현재 노드 의 하위 노드 를 다시 재 귀적 으로 실행 합 니 다.최종 적 으로 전체 AST 트 리 를 실행 합 니 다.
    구체 적 인 코드 를 확인 해 보도 록 하 겠 습 니 다.
    
    const evaluate = (node: t.Node, scope) => {
     const evalFunc = evaluateMap[node.type];
     if (!evalFunc) {
     throw `${node.loc} ${node.type}     `;
     }
     return evalFunc(node, scope);
    }
    
    이상 은 Evauite 가 구체 적 으로 한 일이 다.
    그 중에서 evaluate Map 은 현재 실 현 된 내용 집합 입 니 다.구체 적 인 코드 를 살 펴 보 겠 습 니 다.
    
    const evaluateMap: EvaluateMap = {
     File(node: t.File, scope) {
     evaluate(node.program, scope);
     },
    
     Program(node: t.Program, scope) {
     for (const n of node.body) {
      evaluate(n, scope);
     }
     },
    
     Identifier(node: t.Identifier, scope) {
     const $var = scope.$find(node.name);
     if (!$var) {
      throw `[Error] ${node.loc}, '${node.name}'    `;
     }
     return $var.$get();
     },
    
     StringLiteral(node: t.StringLiteral, scope) {
     return node.value;
     },
    
     NumericLiteral(node: t.NumericLiteral, scope) {
     return node.value;
     },
    
     BooleanLiteral(node: t.BooleanLiteral, scope) {
     return node.value;
     },
    
     NullLiteral(node: t.NullLiteral, scope) {
     return null;
     },
    
     BlockStatement(block: t.BlockStatement, scope) {
     const blockScope = scope.shared ? scope : new Scope('block', scope);
     for (const node of block.body) {
      const res = evaluate(node, blockScope);
      if (res === BREAK || res === CONTINUE || res === RETURN) {
      return res;
      }
     }
     },
    
     DebuggerStatement(node: t.DebuggerStatement, scope) {
     debugger;
     },
    
     ExpressionStatement(node: t.ExpressionStatement, scope) {
     evaluate(node.expression, scope);
     },
    
     ReturnStatement(node: t.ReturnStatement, scope) {
     RETURN.result = (node.argument ? evaluate(node.argument, scope) : void 0);
     return RETURN;
     },
    
     BreakStatement(node: t.BreakStatement, scope) {
     return BREAK;
     },
    
     ContinueStatement(node: t.ContinueStatement, scope) {
     return CONTINUE;
     },
    
     IfStatement(node: t.IfStatement, scope) {
     if (evaluate(node.test, scope)) {
      return evaluate(node.consequent, scope);
     }
    
     if (node.alternate) {
      const ifScope = new Scope('block', scope, true);
      return evaluate(node.alternate, ifScope)
     }
     },
    
     SwitchStatement(node: t.SwitchStatement, scope) {
     const discriminant = evaluate(node.discriminant, scope);
     const switchScope = new Scope('switch', scope);
     for (const ca of node.cases){
      if (ca.test === null || evaluate(ca.test, switchScope) === discriminant) {
      const res = evaluate(ca, switchScope);
      if (res === BREAK) {
       break;
      } else if (res === RETURN) {
       return res;
      }
      }
     }
     },
    
     SwitchCase(node: t.SwitchCase, scope) {
     for (const item of node.consequent) {
      const res = evaluate(item, scope);
      if (res === BREAK || res === RETURN) {
      return res;
      }
     }
     },
    
     ThrowStatement(node: t.ThrowStatement, scope) {
     throw evaluate(node.argument, scope);
     },
    
     TryStatement(node: t.TryStatement, scope) {
     try {
      return evaluate(node.block, scope);
     } catch (error) {
      if (node.handler) {
      const catchScope = new Scope('block', scope, true);
      catchScope.$let((<t.Identifier>node.handler.param).name, error);
      return evaluate(node.handler, catchScope);
      } else {
      throw error;
      }
     } finally {
      if (node.finalizer) {
      return evaluate(node.finalizer, scope);
      }
     }
     },
    
     CatchClause(node: t.CatchClause, scope) {
     return evaluate(node.body, scope);
     },
    
     WhileStatement(node: t.WhileStatement, scope) {
     while (evaluate(node.test, scope)) {
      const whileScope = new Scope('loop', scope, true);
      const res = evaluate(node.body, whileScope);
      if (res === CONTINUE) continue;
      if (res === BREAK) break;
      if (res === RETURN) return res;
     }
     },
    
     ForStatement(node: t.ForStatement, scope) {
     for (
      const forScope = new Scope('loop', scope),
      initVal = evaluate(node.init, forScope);
      evaluate(node.test, forScope);
      evaluate(node.update, forScope)
     ) {
      const res = evaluate(node.body, forScope);
      if (res === CONTINUE) continue;
      if (res === BREAK) break;
      if (res === RETURN) return res;
     }
     },
    
     ForInStatement(node: t.ForInStatement, scope) {
     const kind = (<t.VariableDeclaration>node.left).kind;
     const decl = (<t.VariableDeclaration>node.left).declarations[0];
     const name = (<t.Identifier>decl.id).name;
    
     for (const value in evaluate(node.right, scope)) {
      const forScope = new Scope('loop', scope, true);
      scope.$define(kind, name, value);
      const res = evaluate(node.body, forScope);
      if (res === CONTINUE) continue;
      if (res === BREAK) break;
      if (res === RETURN) return res;
     }
     },
    
     ForOfStatement(node: t.ForOfStatement, scope) {
     const kind = (<t.VariableDeclaration>node.left).kind;
     const decl = (<t.VariableDeclaration>node.left).declarations[0];
     const name = (<t.Identifier>decl.id).name;
    
     for (const value of evaluate(node.right, scope)) {
      const forScope = new Scope('loop', scope, true);
      scope.$define(kind, name, value);
      const res = evaluate(node.body, forScope);
      if (res === CONTINUE) continue;
      if (res === BREAK) break;
      if (res === RETURN) return res;
     }
     },
    
     FunctionDeclaration(node: t.FunctionDeclaration, scope) {
     const func = evaluateMap.FunctionExpression(node, scope);
     scope.$var(node.id.name, func);
     },
    
     VariableDeclaration(node: t.VariableDeclaration, scope) {
     const { kind, declarations } = node;
     for (const decl of declarations) {
      const varName = (<t.Identifier>decl.id).name;
      const value = decl.init ? evaluate(decl.init, scope) : void 0;
      if (!scope.$define(kind, varName, value)) {
      throw `[Error] ${name}     `
      }
     }
     },
    
     ThisExpression(node: t.ThisExpression, scope) {
     const _this = scope.$find('this');
     return _this ? _this.$get() : null;
     },
    
     ArrayExpression(node: t.ArrayExpression, scope) {
     return node.elements.map(item => evaluate(item, scope));
     },
    
     ObjectExpression(node: t.ObjectExpression, scope) {
     let res = Object.create(null);
     node.properties.forEach((prop) => {
      let key;
      let value;
      if(prop.type === 'ObjectProperty'){
      key = prop.key.name;
      value = evaluate(prop.value, scope);
      res[key] = value;
      }else if (prop.type === 'ObjectMethod'){
      const kind = prop.kind;
      key = prop.key.name;
      value = evaluate(prop.body, scope);
      if(kind === 'method') {
       res[key] = value;
      }else if(kind === 'get') {
       Object.defineProperty(res, key, { get: value });
      }else if(kind === 'set') {
       Object.defineProperty(res, key, { set: value });
      }
      }else if(prop.type === 'SpreadElement'){
      const arg = evaluate(prop.argument, scope);
      res = Object.assign(res, arg);
      }
     });
     return res;
     },
    
     FunctionExpression(node: t.FunctionExpression, scope) {
     return function (...args: any) {
      const funcScope = new Scope('function', scope, true);
      node.params.forEach((param: t.Identifier, idx) => {
      const { name: paramName } = param;
      funcScope.$let(paramName, args[idx]);
      });
      funcScope.$const('this', this);
      funcScope.$const('arguments', arguments);
      const res = evaluate(node.body, funcScope);
      if (res === RETURN) {
      return res.result;
      }
     }
     },
    
     ArrowFunctionExpression(node: t.ArrowFunctionExpression, scope) {
     return (...args) => {
      const funcScope = new Scope('function', scope, true);
      node.params.forEach((param: t.Identifier, idx) => {
      const { name: paramName } = param;
      funcScope.$let(paramName, args[idx]);
      });
      const _this = funcScope.$find('this');
      funcScope.$const('this', _this ? _this.$get() : null);
      funcScope.$const('arguments', args);
      const res = evaluate(node.body, funcScope);
      if (res === RETURN) {
      return res.result;
      }
     }
     },
    
     UnaryExpression(node: t.UnaryExpression, scope) {
     const expressionMap = {
      '~': () => ~evaluate(node.argument, scope),
      '+': () => +evaluate(node.argument, scope),
      '-': () => -evaluate(node.argument, scope),
      '!': () => !evaluate(node.argument, scope),
      'void': () => void evaluate(node.argument, scope),
      'typeof': () => {
      if (node.argument.type === 'Identifier') {
       const $var = scope.$find(node.argument.name);
       const value = $var ? $var.$get() : void 0;
       return typeof value;
      }
      return typeof evaluate(node.argument, scope);
      },
      'delete': () => {
      if (node.argument.type === 'MemberExpression') {
       const { object, property, computed } = node.argument;
       const obj = evaluate(object, scope);
       let prop;
       if (computed) {
       prop = evaluate(property, scope);
       } else {
       prop = property.name;
       }
       return delete obj[prop];
      } else {
       throw '[Error]     '
      }
      },
     }
     return expressionMap[node.operator]();
     },
    
     UpdateExpression(node: t.UpdateExpression, scope) {
     const { prefix, argument, operator } = node;
     let $var: IVariable;
     if (argument.type === 'Identifier') {
      $var = scope.$find(argument.name);
      if (!$var) throw `${argument.name}    `;
     } else if (argument.type === 'MemberExpression') {
      const obj = evaluate(argument.object, scope);
      let prop;
      if (argument.computed) {
      prop = evaluate(argument.property, scope);
      } else {
      prop = argument.property.name;
      }
      $var = {
      $set(value: any) {
       obj[prop] = value;
       return true;
      },
      $get() {
       return obj[prop];
      }
      }
     } else {
      throw '[Error]     '
     }
    
     const expressionMap = {
      '++': v => {
      $var.$set(v + 1);
      return prefix ? ++v : v++
      },
      '--': v => {
      $var.$set(v - 1);
      return prefix ? --v : v--
      },
     }
    
     return expressionMap[operator]($var.$get());
     },
    
     BinaryExpression(node: t.BinaryExpression, scope) {
     const { left, operator, right } = node;
     const expressionMap = {
      '==': (a, b) => a == b,
      '===': (a, b) => a === b,
      '>': (a, b) => a > b,
      '<': (a, b) => a < b,
      '!=': (a, b) => a != b,
      '!==': (a, b) => a !== b,
      '>=': (a, b) => a >= b,
      '<=': (a, b) => a <= b,
      '<<': (a, b) => a << b,
      '>>': (a, b) => a >> b,
      '>>>': (a, b) => a >>> b,
      '+': (a, b) => a + b,
      '-': (a, b) => a - b,
      '*': (a, b) => a * b,
      '/': (a, b) => a / b,
      '&': (a, b) => a & b,
      '%': (a, b) => a % b,
      '|': (a, b) => a | b,
      '^': (a, b) => a ^ b,
      'in': (a, b) => a in b,
      'instanceof': (a, b) => a instanceof b,
     }
     return expressionMap[operator](evaluate(left, scope), evaluate(right, scope));
     },
    
     AssignmentExpression(node: t.AssignmentExpression, scope) {
     const { left, right, operator } = node;
     let $var: IVariable;
    
     if (left.type === 'Identifier') {
      $var = scope.$find(left.name);
      if(!$var) throw `${left.name}    `;
     } else if (left.type === 'MemberExpression') {
      const obj = evaluate(left.object, scope);
      let prop;
      if (left.computed) {
      prop = evaluate(left.property, scope);
      } else {
      prop = left.property.name;
      }
      $var = {
      $set(value: any) {
       obj[prop] = value;
       return true;
      },
      $get() {
       return obj[prop];
      }
      }
     } else {
      throw '[Error]     '
     }
    
     const expressionMap = {
      '=': v => { $var.$set(v); return $var.$get() },
      '+=': v => { $var.$set($var.$get() + v); return $var.$get() },
      '-=': v => { $var.$set($var.$get() - v); return $var.$get() },
      '*=': v => { $var.$set($var.$get() * v); return $var.$get() },
      '/=': v => { $var.$set($var.$get() / v); return $var.$get() },
      '%=': v => { $var.$set($var.$get() % v); return $var.$get() },
      '<<=': v => { $var.$set($var.$get() << v); return $var.$get() },
      '>>=': v => { $var.$set($var.$get() >> v); return $var.$get() },
      '>>>=': v => { $var.$set($var.$get() >>> v); return $var.$get() },
      '|=': v => { $var.$set($var.$get() | v); return $var.$get() },
      '&=': v => { $var.$set($var.$get() & v); return $var.$get() },
      '^=': v => { $var.$set($var.$get() ^ v); return $var.$get() },
     }
    
     return expressionMap[operator](evaluate(right, scope));
     },
    
     LogicalExpression(node: t.LogicalExpression, scope) {
     const { left, right, operator } = node;
     const expressionMap = {
      '&&': () => evaluate(left, scope) && evaluate(right, scope),
      '||': () => evaluate(left, scope) || evaluate(right, scope),
     }
     return expressionMap[operator]();
     },
    
     MemberExpression(node: t.MemberExpression, scope) {
     const { object, property, computed } = node;
     const obj = evaluate(object, scope);
     let prop;
     if (computed) {
      prop = evaluate(property, scope);
     } else {
      prop = property.name;
     }
     return obj[prop];
     },
    
     ConditionalExpression(node: t.ConditionalExpression, scope) {
     const { test, consequent, alternate } = node;
     return evaluate(test, scope) ? evaluate(consequent, scope) : evaluate(alternate, scope);
     },
    
     CallExpression(node: t.CallExpression, scope) {
     const func = evaluate(node.callee, scope);
     const args = node.arguments.map(arg => evaluate(arg, scope));
     let _this;
     if (node.callee.type === 'MemberExpression') {
      _this = evaluate(node.callee.object, scope);
     } else {
      const $var = scope.$find('this');
      _this = $var ? $var.$get() : null;
     }
     return func.apply(_this, args);
     },
    
     NewExpression(node: t.NewExpression, scope) {
     const func = evaluate(node.callee, scope);
     const args = node.arguments.map(arg => evaluate(arg, scope));
     return new (func.bind(func, ...args));
     },
    
     SequenceExpression(node: t.SequenceExpression, scope) {
     let last;
     node.expressions.forEach(expr => {
      last = evaluate(expr, scope);
     })
     return last;
     },
    }
    
    
    이상,evaluate-apply 이 과정 은 끝 났 습 니 다.
    scope
    scope 가 어떻게 실현 되 는 지 다시 한번 봅 시다.
    
    class Scope implements IScope {
     public readonly variables: EmptyObj = Object.create(null);
    
     constructor(
     private readonly scopeType: ScopeType,
     private parent: Scope = null,
     public readonly shared = false,
     ) { }
    }
    
    
    우 리 는 scope 를 모 의 하 는 종 류 를 만 들 었 다.Scope 류 는 다음 과 같은 4 개의 속성 을 포함 하고 있 음 을 알 수 있 습 니 다.
  • variables:현재 환경 에 존재 하 는 변수
  • scope Type:현재 환경의 type
  • parent:현재 환경의 부모 환경
  • shared:어떤 때 는 서브 환경 을 중복 할 필요 가 없 기 때문에 이 표지
  • 를 사용 합 니 다.
    다음은 환경 에서 변 수 를 어떻게 설명 하 는 지 살 펴 보 겠 습 니 다.
    우선 클래스 를 만들어 변 수 를 모 의 합 니 다.
    
    class Variable implements IVariable {
     constructor(
     private kind: Kind,
     private value: any
     ){ }
    
     $get() {
     return this.value
     }
    
     $set(value: any) {
     if (this.kind === 'const') {
      return false
     }
     this.value = value;
     return true;
     }
    }
    
    
    이 종류 에는 두 가지 속성 과 두 가지 방법 이 있다.
  • kind 는 이 변 수 를 var,let 또는 const 성명
  • 을 통 해 표시 하 는 데 사 용 됩 니 다.
  • value 는 이 변수의 값 을 나타 낸다
  • $get 과$set 는 각각 이 변수의 값 을 가 져 오고 설정 하 는 데 사 용 됩 니 다
  • Variable 클래스 가 있 으 면 Scope 클래스 의 성명 변 수 를 만 드 는 방법 을 만 들 수 있 습 니 다.
    let 와 const 의 성명 방식 은 기본적으로 같다.
    
    $const(varName: string, value: any) {
     const variable = this.variables[varName];
     if (!variable) {
     this.variables[varName] = new Variable('const', value);
     return true;
     }
     return false;
    }
    
    $let(varName: string, value: any) {
     const variable = this.variables[varName];
     if (!variable) {
     this.variables[varName] = new Variable('let', value);
     return true;
     }
     return false;
    }
    
    
    var 의 성명 방식 은 약간 차이 가 있 습 니 다.js 에 서 는 function 을 제외 하고 var 로 설명 하 는 변 수 는 부모 역할 영역(js 의 역사 남 겨 진 구덩이)으로 밝 혀 지기 때 문 입 니 다.코드 좀 봅 시다.
    
    $var(varName: string, value: any) {
     let scope: Scope = this;
     while (!!scope.parent && scope.scopeType !== 'function') {
     scope = scope.parent;
     }
     const variable = scope.variables[varName];
     if (!variable) {
     scope.variables[varName] = new Variable('var', value);
     } else {
     scope.variables[varName] = variable.$set(value);
     }
     return true
    }
    
    성명 을 제외 하고 우 리 는 변 수 를 찾 는 방법 이 필요 합 니 다.이 방법 은 현재 환경 에서 부터 역할 도 메 인 체인 을 따라 가장 바깥쪽 환경 을 찾 을 때 까지 입 니 다.따라서 코드 는 다음 과 같다.
    
    $find(varName: string): null | IVariable {
     if (Reflect.has(this.variables, varName)) {
     return Reflect.get(this.variables, varName);
     }
     if (this.parent) {
     return this.parent.$find(varName);
     }
     return null;
    }
    이상,기본 javascript 원 순환 값 계산기 가 완성 되 었 습 니 다.
    마지막.
    codesandbox온라인 으로 체험 해 보 세 요.
    전체 프로젝트 주 소 는nvwajs입 니 다.채찍 을 환영 합 니 다.스타 를 환영 합 니 다.
    레 퍼 런 스
    《SICP》
    위 챗 애플 릿 도 열 경 코드 를 강행 해 야 합 니 다.거위 공장 은 당신 이 나 를 항문 으로 보 내 는 것 에 불복 합 니 다.
    자 바스 크 립 트 원 순환 값 계산 기 를 만 드 는 방법 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.자 바스 크 립 트 원 순환 값 계산 기 에 관 한 더 많은 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 많은 지원 을 바 랍 니 다!

    좋은 웹페이지 즐겨찾기