Как бы я реализовал простой язык программирования на основе стека

Я заинтересован в расширении моих знаний в области компьютерного программирования путем реализации стекового языка программирования. Я ищу совет о том, с чего начать, так как намерен, чтобы у него были такие функции, как "pushint 1"который поместил бы целое число со значением 1 в верхнюю часть стека и управлял потоком с помощью меток вроде"L01: jump L01:".

До сих пор я реализовал на C# то, что я хочу, чтобы мой язык действовал (хотел связать с ним, но IDEOne заблокирован), но он очень грязный и требует оптимизации. Он переводит ввод в XML и затем анализирует его. Мои цели - перейти на язык более низкого уровня (возможно, C/C++), но мои проблемы заключаются в реализации стека, который может содержать различные типы данных и не имеет фиксированного размера.

В конце концов я также хотел бы реализовать массивы и функции. Кроме того, я думаю, что мне нужен лучший Lexer, и мне интересно, будет ли дерево разбора хорошей идеей для такого упрощенного языка.

Любые советы / критика приветствуются, и, пожалуйста, учтите, что я все еще достаточно новичок в программировании (я только недавно закончил AP CompSci I). Также приветствуются ссылки на языки на основе стека с открытым исходным кодом.

Вот основная программа, которую я хотел бы попробовать интерпретировать / скомпилировать (где [this is a comment]):

[Hello World!]
pushchar    '\n'
pushstring  "Hello World!"
print
[Count to 5 and then count down!]
pushint     1
setlocal    0
L01:
pushchar    '\n'
getlocal    0
print           [print x + '\n']
getlocal    0
increment
setlocal    0   [x = x + 1]
pushint     5
getlocal    0
lessthan        [x < 5]
iftrue      L01
L02:
pushchar    '\n'
getlocal    0
print           [print x + '\n']
getlocal    0
decrement
setlocal    0   [x = x - 1]
pushint     0
getlocal    0
greaterthan     [x > 0]
iftrue      L02

Ожидаемый результат будет:

Hello World!
1
2
3
4
5
4
3
2
1

2 ответа

Решение

Язык на основе стека, такой как Factor, имеет следующий синтаксис:

2 3 + 5 - print

Это эквивалентно следующему коду стиля C:

print(2 + 3 - 5);

Преимущество использования стекового языка в том, что он прост в реализации. Кроме того, если язык использует обратную польскую нотацию, как это делает большинство языков, основанных на стеке, тогда все, что вам нужно для внешнего интерфейса вашего языка, - это лексер. Вам не нужно анализировать токены в синтаксическое дерево, так как есть только один способ декодировать поток токенов.

Вы пытаетесь создать не язык программирования на основе стека, а виртуальную машину на основе стека. Виртуальные машины приложений могут быть либо на основе стека, либо на основе регистров. Например, виртуальная машина Java основана на стеке. Он выполняет Java-байт-код (это то, что вы создаете - байт-код для виртуальной машины). Однако языки программирования, которые компилируются в этот байт-код (например, Java, Erlang, Groovy и т. Д.), Не основаны на стеке.

То, что вы пытаетесь создать, похоже на язык уровня сборки вашей собственной виртуальной машины, который основан на стеке. Тем не менее, это будет довольно легко сделать - виртуальные машины на основе стека проще реализовать с помощью виртуальных машин на основе регистра. Опять же, все, что вам нужно, это лексер, такой как flex. Вот небольшой пример в JavaScript с использованием библиотеки под названием lexer:

var program = "[print(2 + 3)]";
program += "\n push 2";
program += "\n push 3";
program += "\n add";
program += "\n print";

lexer.setInput(program);

var token;
var stack = [];
var push = false;

while (token = lexer.lex()) {
    switch (token) {
    case "NUMBER":
        if (push) stack.push(lexer.yytext);
        else alert("Unexpected number.");
        break;
    case "ADD":
        if (push) alert("Expected number.");
        else stack.push(stack.pop() + stack.pop());
        break;
    case "PRINT":
        if (push) alert("Expected number.");
        else alert(stack.pop());
        break;
    }

    push = token === "PUSH";
}
<script src="https://rawgit.com/aaditmshah/lexer/master/lexer.js"></script>
<script>
var lexer = new Lexer;

lexer.addRule(/\s+/, function () {
    // matched whitespace - discard it
});

lexer.addRule(/\[.*\]/, function () {
    // matched a comment - discard it
});

lexer.addRule(/\d+/, function (lexeme) {
    this.yytext = parseInt(lexeme);
    return "NUMBER";
});

lexer.addRule(/push/, function () {
    return "PUSH";
});

lexer.addRule(/add/, function () {
    return "ADD";
});

lexer.addRule(/print/, function () {
    return "PRINT";
});
</script>

Это действительно просто. Вы можете поиграть с программой и изменить ее в соответствии со своими потребностями. Удачи.

Я думаю, что вы найдете статью о "MetaII" действительно поучительной. Он показывает, как определить машину компилятора стека pushdown и компилятор для нее, на 10 коротких, но изнурительных страницах. Смотрите этот ответ: /questions/25407486/uchebnyie-resursyi-po-parseram-interpretatoram-i-kompilyatoram/25407526#25407526 Как только вы это поймете, написание интерпретаторов стека pushdown будет всегда легко.

Другие вопросы по тегам