#javascript #scheme #lisp
#javascript #схема #lisp
Вопрос:
У меня есть scheme на JavaScript (называется LIPS), и я пишу многострочный интерпретатор, используя терминал jQuery и недавно созданный пример для многострочной команды, код просто предотвращает поведение клавиши 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 = /(.*(ifs )(/;
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, в ней есть нажатие клавиш и keydown, которые вы можете игнорировать, важны keymap.ENTER
и indent
функция.
Мой вопрос в том, как мне следует действовать и реализовать отступы в scheme? Каковы правила? Я думаю, что смогу заставить это работать, если буду знать алгоритм, но, вероятно, есть много крайних случаев, как должен работать отступ.
Мой базовый код делает отступ только на 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
единственный специальный случай отступа?
Комментарии:
1. Я думаю, что это не слишком широко, потому что синтаксис lips (особенно scheme) очень прост, поэтому не должно быть много случаев, только список форм (всего несколько) и уровень отступа для следующей строки.
2. Мне больше всего интересно, почему эта функция не написана в самой LIPS; но помимо этого, вы также могли бы взглянуть на github.com/slime/slime/blob/master/contrib / … о том, как вы можете использовать DSL для описания уровней отступов и т.д. Правила см., возможно community.schemewiki.org/?scheme-style
3. @coredump спасибо, что вторая ссылка, это очень помогло. И я не использовал lips для создания отступа, потому что интерпретатор находится на JS. Мне нужно было бы написать интерпретатор на языке lips, но это также пример того, как использовать jQuery Terminal. Возможно, я перенесу этот код в Codepen и в будущем создам интерпретатор в Lips.
Ответ №1:
Стандартный отступ меняется в зависимости от того, где ставятся новые строки. Отступ выравнивается таким образом, что означает:
(one
two
three)
(one two
three)
Ваши этого не делают. Каким-то образом вы используете отступ в два пробела для специальных форм, который создает неявное начало.
(define
two)
(define two
three)
Теперь правила, когда это применимо, в основном касаются всего синтаксиса, где у вас есть неявные begin
like begin
, define
, lambda
let
и friends. Я думаю, DrRacket делает это с каждой привязкой, начинающейся с «def» abd «begin», такой, которую вы можете создать def-system-call
, и на самом деле отступ будет таким, как define
while letx
этого не делает.
На это могло быть какое-то указание в определении дополнительного синтаксиса. Например. В Common Lisp вы можете использовать amp;body
вместо amp;rest
в макросах, и они представляют остальные элементы с тем отличием, которое amp;body
только что указало, что он должен иметь отступ в 2 пробела, как и специальные формы, подобные defun
. Поскольку вы создаете свой собственный язык, вы могли бы включить что-то подобное в свой язык 🙂
Комментарии:
1. Вы проверили мою текущую реализацию? Я создал код на основе ссылки @coredump в jcubic.github.io/lips добавлю это в качестве ответа, отступ будет точно таким же, как в ваших 4 примерах. (за исключением lambda как специального, нужно добавить его в список специальных).
2. @jcubic Я только поиграл с code pen и предположил, что вы обновили его, когда обновили вопрос.
3. Я обновил вопрос и, думаю, также codpen, но затем @coredump добавил ссылку с синтаксисом, и я создал код на основе этого синтаксиса и забыл обновить pen и question.
Ответ №2:
Вот мой рабочий отступ, основанный на ссылке @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 amp;amp; 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.