Как написать функцию JavaScript, которая будет правильно делать отступы в коде scheme?

#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.