我有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.ENTER
和indent
功能。
我的问题是我应该如何去实施计划缩进?规则是什么?我认为如果我知道算法,我将能够使它工作,但可能有很多边缘情况,缩进应该如何工作。
我的基本代码只为每个换行符缩进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
唯一的特殊缩进案例?
标准缩进随换行符的位置而变化。缩进对齐,这意味着:
(one
two
three)
(one two
three)
你的不这样做。不知何故,你使用两个空格缩进作为隐式开始的特殊形式。
(define
two)
(define two
three)
现在适用的时候的规则基本上都是你有begin
,begin
,define
,lambda
和朋友的隐式let
的语法。我认为DrRacket以“def”abd“begin”开头的每个绑定做到这一点,这样你就可以制作def-system-call
,它实际上会像define
那样缩进,而letx
却没有。
它可能在额外语法的定义中有一些指示。例如。在Common Lisp中,您可以在宏中使用&body
而不是&rest
,它们代表其余的元素,区别在于&body
只是表示它应该具有2个空格缩进,就像defun
这样的特殊形式。既然你正在制作自己的语言,你可能会用你的语言包含这样的东西:)
这是我基于@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上看到。