如何编写能正确缩进方案代码的JavaScript函数?

问题描述 投票:2回答:2

我有JavaScript方案(称为LIPS),我正在编写多线解释器,使用jQuery Terminal和新创建的多行命令示例,代码简单防止了enter键的默认行为。

我的lisp的问题是,如果没有输入的自动缩进,它看起来不太好,就像在GNU Emacs中一样。所以我写了简单的自动缩进,但我不知道如何让它与GNU Emacs一样工作。我正在寻找和lisp-mode的源代码,但我不是emacs lisp专家,代码不知道什么是缩进的正确逻辑。

这是我的起始代码:

// lisp indentation function
function indent(term, level, offset) {
    // offset is for prompt on first line
    // level if for single indent of next line

    // function return code before cursor
    // to the beginning of the command
    var code = term.before_cursor(); 
    var lines = code.split('\n');
    var prev_line = lines[lines.length - 1];
    var parse = prev_line.match(/^(\s*)(.*)/);
    var spaces = parse[1].length || offset;
    var re_if = /(.*\(if\s+)\(/;
    var m = prev_line.match(re_if);
    if (m) {
        spaces = m[1].length;
    } else if (parse[2].match(/\(/)) {
        spaces += level;
    }
    return spaces;
}
var term = $(selector).terminal(function(code, term) {
    lips.exec(code, env).then(function(ret) {
        ret.forEach(function(ret) {
            if (ret !== undefined) {
                env.get('print').call(env, ret);
            }
        });
    }).catch(function(e) {
        term.error(e.message);
    });
}, {
    name: 'lisp',
    prompt: 'lips> ',
    enabled: false,
    greetings: false,
    keymap: {
        ENTER: function(e, original) {
            if (lips.balanced_parenthesis(this.get_command())) {
                original();
            } else {
                var i = indent(this, 3, this.get_prompt().length);
                this.insert('\n' + (new Array(i + 1).join(' ')));
            }
        }
    }
});

这是我的codepen demo它有keypress和keydown你可以忽略,重要的是keymap.ENTERindent功能。

我的问题是我应该如何去实施计划缩进?规则是什么?我认为如果我知道算法,我将能够使它工作,但可能有很多边缘情况,缩进应该如何工作。

我的基本代码只为每个换行符缩进2个空格,并在if后对齐第一个括号,但只有第一行,因为它只检查上一行。

使用手柄的辅助函数是tokenize(code: string, extended: boolean),它使用{token, offset}返回字符串或对象的数组(offset是字符串内的标记的索引)。

更新:

这是我更新的代码,唯一特别的是if它现在适用于多线。

   // return S-Expression that's at the end (the one you're in)
   function sexp(tokens) {
       var count = 1;
       var i = tokens.length;
       while (count > 0) {
           token = tokens[--i];
           if (!token) {
               return;
           }
           if (token.token === '(') {
               count--;
           } else if (token.token == ')') {
               count++;
           }
       }
       return tokens.slice(i);
   }
   // basic indent
   function indent(term, level, offset) {
       var code = term.before_cursor();
       var tokens = lips.tokenize(code, true);
       var last_sexpr = sexp(tokens);
       var lines = code.split('\n');
       var prev_line = lines[lines.length - 1];
       var parse = prev_line.match(/^(\s*)/);
       var spaces = parse[1].length || offset;
       if (last_sexpr) {
           if (last_sexpr[0].line > 0) {
               offset = 0;
           }
           if (['define', 'begin'].indexOf(last_sexpr[1].token) !== -1) {
               return offset + last_sexpr[0].col + level;
           } else {
               // ignore first 2 tokens - (fn
               var next_tokens = last_sexpr.slice(2);
               for (var i in next_tokens) {
                   var token = next_tokens[i];
                   if (token.token.trim()) {
                       // indent of first non space after function
                       return token.col;
                   }
               }
           }
       }
       return spaces + level;
   }

代码可以在这里测试:https://jcubic.github.io/lips/我错过了一些边缘案例,还是if唯一的特殊缩进案例?

javascript scheme lisp
2个回答
0
投票

标准缩进随换行符的位置而变化。缩进对齐,这意味着:

(one
 two
 three)

(one two
     three)

你的不这样做。不知何故,你使用两个空格缩进作为隐式开始的特殊形式。

(define
  two)

(define two
  three)

现在适用的时候的规则基本上都是你有beginbegindefinelambda和朋友的隐式let的语法。我认为DrRacket以“def”abd“begin”开头的每个绑定做到这一点,这样你就可以制作def-system-call,它实际上会像define那样缩进,而letx却没有。

它可能在额外语法的定义中有一些指示。例如。在Common Lisp中,您可以在宏中使用&body而不是&rest,它们代表其余的元素,区别在于&body只是表示它应该具有2个空格缩进,就像defun这样的特殊形式。既然你正在制作自己的语言,你可能会用你的语言包含这样的东西:)


0
投票

这是我基于@coredump链接的工作缩进:

function sexp(tokens) {
    var count = 1;
    var i = tokens.length;
    while (count > 0) {
        token = tokens[--i];
        if (!token) {
            return;
        }
        if (token.token === '(') {
            count--;
        } else if (token.token == ')') {
            count++;
        }
    }
    return tokens.slice(i);
}
function indent(term, level, offset) {
    var code = term.before_cursor();
    var tokens = lips.tokenize(code, true);
    var last_sexpr = sexp(tokens);
    var lines = code.split('\n');
    var prev_line = lines[lines.length - 1];
    var parse = prev_line.match(/^(\s*)/);
    var spaces = parse[1].length || offset;
    if (last_sexpr) {
        if (last_sexpr[0].line > 0) {
            offset = 0;
        }
        if (last_sexpr.length === 1) {
            return offset + last_sexpr[0].col + 1;
        } else if (['define', 'lambda', 'let'].indexOf(last_sexpr[1].token) !== -1) {
            return offset + last_sexpr[0].col + level;
        } else if (last_sexpr[0].line < last_sexpr[1].line) {
            return offset + last_sexpr[0].col + 1;
        } else if (last_sexpr.length > 3 && last_sexpr[1].line === last_sexpr[3].line) {
            if (last_sexpr[1].token === '(') {
                return offset + last_sexpr[1].col;
            }
            return offset + last_sexpr[3].col;
        } else if (last_sexpr[0].line === last_sexpr[1].line) {
            return offset + last_sexpr[1].col;
        } else {
            var next_tokens = last_sexpr.slice(2);
            for (var i in next_tokens) {
                var token = next_tokens[i];
                if (token.token.trim()) {
                    return token.col;
                }
            }
        }
    }
    return spaces + level;
}

可以在LIPS homepage上看到。

© www.soinside.com 2019 - 2024. All rights reserved.