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

时间:2019-03-28 23:38:35

标签: javascript scheme lisp

我有使用JavaScript的方案(称为LIPS),并且正在使用jQuery Terminal和新创建的multline命令示例编写多行解释器,该代码简单可防止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,它具有您可以忽略的按键和按下键,重要的是keymap.ENTERindent功能。

我的问题是我应该如何实施方案缩进?都有些什么样的规矩?我认为,只要知道算法,我就能使它起作用,但是可能存在很多极端情况,缩进应该如何起作用。

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

可以使用的辅助函数是tokenize(code: string, extended: boolean),它返回带有{token, 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是唯一的特殊缩进情况?

2 个答案:

答案 0 :(得分: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。由于您使用的是自己的语言,因此可能会在您的语言中包含以下内容:)

答案 1 :(得分: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上看到