You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Codewars Tiny Three-Pass Compiler JS堆内存溢出问题求助

解决Codewars Tiny Three-Pass Compiler练习中的Node.js内存溢出问题

问题现象

完成Tiny Three-Pass Compiler练习时,在Node.js v10环境下出现以下错误:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory Aborted (core dumped) <--- Last few GCs --->
[19:0x5578df6e1f80] 6093 ms: Mark-sweep 580.2 (592.5) -> 580.2 (584.5) MB, 1501.0 / 0.0 ms (average mu = 0.282, current mu = 0.000) last resort GC in old space requested [19:0x5578df6e1f80] 6285 ms: Mark-sweep 580.2 (584.5) -> 580.2 (584.5) MB, 191.9 / 0.0 ms (average mu = 0.243, current mu = 0.000) last resort GC in old space requested
<--- JS stacktrace --->
==== JS stack trace =========================================
0: ExitFrame [pc: 0x3dbb90e5be1d] Security context: 0x3dd3dcdf3419
1: /* anonymous / [0x3d354a868c89 ,program=0x1344e3ade909 <String[47]: [ x y z ] ( 23x +5y -3z ) / (1 +3 +22)>]
2: /* anonymous */ [0x3d354a8463c9] [/home/codewarrior/node/test.js:214] [bytecode=0x3dd3dcdce859 offset=90](this=0x3d354...`

在Node.js v8环境下则出现:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Aborted (core dumped)

当前代码

function isNumber (token){
  return !isNaN(token);
}
function isOperator(token){
  return "*/+-"indexOf(token)!== -1;
}

function Compiler () {};

Compiler.prototype.compile = function (program) {
  return this.pass3(this.pass2(this.pass1(program)));
};

Compiler.prototype.tokenize = function (program) {
  // Turn a program string into an array of tokens.  Each token
  // is either '[', ']', '(', ')', '+', '-', '*', '/', a variable
  // name or a number (as a string)
  var regex = /\s*([-+*/\(\)\[\]]|[A-Za-z]+|[0-9]+)\s*/g;
  return program.replace(regex, ":$1").substring(1).split(':').map( function (tok) {
    return isNaN(tok) ? tok : tok|0;
  });
};

Compiler.prototype.pass1 = function (program) {
  var tokens = this.tokenize(program);
  
  function getNextToken(){
    token = tokens.shift();
  }
  function precedenceIsNotGreater(o1, o2){
    var precedences = {
      '/' :4,
      '*' :3,
      '+' :2,
      '-' :1,
    }
    return precedences[o1] <= precedences[o2];
  }
  
  var token ;
  var outputQueue = [];
  var operatorStack = [];
  var args = [];
  
  do{
    getNextToken()
    if(token === '['){
      for(getNextToken(); token !== ']'; getNextToken()){
        args.push(token);
      }
    }else if(isNumber(token) || args.includes(token)){
      outputQueue.push(token);
    }else if(isOperator(token)){
      var o1 = token;
      for(var o2 = operatorStack[operatorStack.length-1]; operatorStack.length && isOperator(o2) && precedenceIsNotGreater(o1, o2); o2 = operatorStack[operatorStack.length -1]){
        outputQueue.push(token);
      }
      operatorStack.push(o1);
    }else if(token === '('){
      operatorStack.push(token);
    } else if(token === ')'){
      for(var nextOperator = operatorStack[operatorStack.length -1]; operatorStack.length && nextOperator !== '('; nextOperator = operatorStack[operatorStack.length-1]){
        outputQueue.push(operatorStack.pop())
      }
      operatorStack.pop()
    }
  } while(tokens.length);
  
  while(operatorStack.length){
    outputQueue.push(operatorStack.pop())
  }
  
  var output;
  
  function getNextOutput(){
    output = outputQueue.pop();
  }
  
  function buildAst(outputQueue) {
    getNextOutput();
    var node = {};
    
    if(isNumber(output)){
      node.op = 'imm';
      node.n = output;
    } else if(args.includes(output)){
      node.op = 'arg';
      node.n = args.indexOf(output);
    } else if(isOperator(output)){
      node.op = output;
      var b = buildAst(outputQueue);
      var a = buildAst(outputQueue);
      node.a = a;
      node.b = b;
    }
    return node;
  }
  return buildAst(outputQueue);
};

Compiler.prototype.pass2 = function (ast) {
  function reduceTree(ast) {
    if(ast.op === 'imm' || ast.op === 'arg') {
      return ast;
    }
    ast.a = reduceTree(ast.a);
    ast.b = reduceTree(ast.b);
    
    if(ast.a.op === 'imm' && ast.b.op === 'imm'){
     var n = Function("return " + ''+ast.a.n+ast.op+ast.b.n)();
      return{ op: 'imm', n: n}
    }
    return ast;
  }
  return reduceTree(ast)
};

Compiler.prototype.pass3 = function (ast) {
  var operatorMap = {
    '+' : 'AD',
    '-' : 'SU',
    '*' : 'MU',
    '/' : 'DI',
  }
  
  var operationDepths = {};
  var maxDepth = -Infinity;
  
  function markDepth (ast, depth =0){
    if(ast.a && ast.b){
      maxDepth = Math.max(maxDepth, depth);
      if(!operationDepths[depth]){
        operationDepths[depth] = [];
      }
      operationDepths[depth].push(ast);
      markDepth(ast.a, depth+1);
      markDepth(ast.b, depth+1);
    }
  }
  markDepth(ast);
  var asm = [];
  
  var currentDepth = maxDepth;
  while(currentDepth >=0){
    currentDepthOperations = operationDepths[currentDepth];
    while(currentDepthOperations.length){
     var currentOperation = currentDepthOperations.shift();
      
      if(currentOperation.b.op === 'imm'){
        asm.push('IM ' + currentOperation.b.n);
      }else if(currentOperation.b.op === 'arg'){
        asm.push('AR ' + currentOperation.b.n)
      }else{
        asm.push('PO')
      }
      
      asm.push('SW')
      
      if(currentOperation.a.aop === 'imm'){
        asm.push('IM ' + currentOperation.a.n)
      }else if(currentOperation.a.op === 'arg') {
        asm.push('AR ' + currentOperation.a.n)
      }else{
        asm.push('PO')
      }
      
      asm.push(operatorMap[currentOperation.op]);
      
      asm.push('PU')
    }
    currentDepth--;
  }
  return asm;
};

问题分析与修复

内存溢出的核心原因是pass1中处理运算符时的死循环,同时代码还有几处语法和逻辑错误,逐一修复如下:

1. 修复isOperator函数的语法错误

原代码中字符串调用indexOf时缺少点运算符,导致语法错误:

// 错误
return "*/+-"indexOf(token)!== -1;
// 修复后
return "*/+-".indexOf(token)!== -1;

2. 修复pass1中的运算符处理死循环

在处理运算符的for循环里,原代码错误地将当前运算符token(即o1)反复加入outputQueue,而没有弹出栈顶的运算符o2,导致队列无限增长,最终内存溢出:

// 错误
outputQueue.push(token);
// 修复后:弹出栈顶运算符并加入队列
outputQueue.push(operatorStack.pop());

3. 修复pass3中的笔误

原代码中currentOperation.a.aop是笔误,应为currentOperation.a.op,否则会导致判断逻辑错误:

// 错误
if(currentOperation.a.aop === 'imm'){
// 修复后
if(currentOperation.a.op === 'imm'){

修复后的完整代码

function isNumber(token) {
  return !isNaN(token);
}
function isOperator(token) {
  return "*/+-".indexOf(token)!== -1;
}

function Compiler() {};

Compiler.prototype.compile = function(program) {
  return this.pass3(this.pass2(this.pass1(program)));
};

Compiler.prototype.tokenize = function(program) {
  var regex = /\s*([-+*/\(\)\[\]]|[A-Za-z]+|[0-9]+)\s*/g;
  return program.replace(regex, ":$1").substring(1).split(':').map(function(tok) {
    return isNaN(tok)? tok : tok | 0;
  });
};

Compiler.prototype.pass1 = function(program) {
  var tokens = this.tokenize(program);
  
  function getNextToken() {
    token = tokens.shift();
  }
  function precedenceIsNotGreater(o1, o2) {
    var precedences = {
      '/' :4,
      '*' :3,
      '+' :2,
      '-' :1,
    }
    return precedences[o1] <= precedences[o2];
  }
  
  var token;
  var outputQueue = [];
  var operatorStack = [];
  var args = [];
  
  do {
    getNextToken();
    if(token === '[') {
      for(getNextToken(); token!== ']'; getNextToken()) {
        args.push(token);
      }
    } else if(isNumber(token) || args.includes(token)) {
      outputQueue.push(token);
    } else if(isOperator(token)) {
      var o1 = token;
      for(var o2 = operatorStack[operatorStack.length-1]; operatorStack.length && isOperator(o2) && precedenceIsNotGreater(o1, o2); o2 = operatorStack[operatorStack.length -1]) {
        outputQueue.push(operatorStack.pop());
      }
      operatorStack.push(o1);
    } else if(token === '(') {
      operatorStack.push(token);
    } else if(token === ')') {
      for(var nextOperator = operatorStack[operatorStack.length -1]; operatorStack.length && nextOperator!== '('; nextOperator = operatorStack[operatorStack.length-1]) {
        outputQueue.push(operatorStack.pop())
      }
      operatorStack.pop();
    }
  } while(tokens.length);
  
  while(operatorStack.length) {
    outputQueue.push(operatorStack.pop())
  }
  
  var output;
  
  function getNextOutput() {
    output = outputQueue.pop();
  }
  
  function buildAst(outputQueue) {
    getNextOutput();
    var node = {};
    
    if(isNumber(output)) {
      node.op = 'imm';
      node.n = output;
    } else if(args.includes(output)) {
      node.op = 'arg';
      node.n = args.indexOf(output);
    } else if(isOperator(output)) {
      node.op = output;
      var b = buildAst(outputQueue);
      var a = buildAst(outputQueue);
      node.a = a;
      node.b = b;
    }
    return node;
  }
  return buildAst(outputQueue);
};

Compiler.prototype.pass2 = function(ast) {
  function reduceTree(ast) {
    if(ast.op === 'imm' || ast.op === 'arg') {
      return ast;
    }
    ast.a = reduceTree(ast.a);
    ast.b = reduceTree(ast.b);
    
    if(ast.a.op === 'imm' && ast.b.op === 'imm') {
     var n = Function("return " + ast.a.n + ast.op + ast.b.n)();
      return { op: 'imm', n: n };
    }
    return ast;
  }
  return reduceTree(ast);
};

Compiler.prototype.pass3 = function(ast) {
  var operatorMap = {
    '+' : 'AD',
    '-' : 'SU',
    '*' : 'MU',
    '/' : 'DI',
  }
  
  var operationDepths = {};
  var maxDepth = -Infinity;
  
  function markDepth(ast, depth = 0) {
    if(ast.a && ast.b) {
      maxDepth = Math.max(maxDepth, depth);
      if(!operationDepths[depth]) {
        operationDepths[depth] = [];
      }
      operationDepths[depth].push(ast);
      markDepth(ast.a, depth + 1);
      markDepth(ast.b, depth + 1);
    }
  }
  markDepth(ast);
  var asm = [];
  
  var currentDepth = maxDepth;
  while(currentDepth >= 0) {
    var currentDepthOperations = operationDepths[currentDepth];
    while(currentDepthOperations.length) {
     var currentOperation = currentDepthOperations.shift();
      
      if(currentOperation.b.op === 'imm') {
        asm.push('IM ' + currentOperation.b.n);
      } else if(currentOperation.b.op === 'arg') {
        asm.push('AR ' + currentOperation.b.n);
      } else {
        asm.push('PO');
      }
      
      asm.push('SW');
      
      if(currentOperation.a.op === 'imm') {
        asm.push('IM ' + currentOperation.a.n);
      } else if(currentOperation.a.op === 'arg') {
        asm.push('AR ' + currentOperation.a.n);
      } else {
        asm.push('PO');
      }
      
      asm.push(operatorMap[currentOperation.op]);
      
      asm.push('PU');
    }
    currentDepth--;
  }
  return asm;
};

内容的提问来源于stack exchange,提问作者Victor Malhotra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 09:55:21