synchronized - это ключевое слово в Java, которое используется для выполнения перед связью в многопоточной среде, чтобы избежать несогласованности памяти и ошибки вмешательства потока.
"Разворачивание цикла" обычно означает заменять повторение последовательностью действий. Цикл:
for (int i = 0; i < 4; ++i) {
a[i] = b[i] + c[i];
}
развернул бы в эквивалент:
a[0] = b[0] + c[0];
a[1] = b[1] + c[1];
a[2] = b[2] + c[2];
a[3] = b[3] + c[3];
кажется мне, что, кто бы ни заключался в кавычки Википедией, использовал фразу в несколько метафорическом смысле. Так, в этом смысле...
Ваш образец обычно вызывался бы в интерпретаторе, который обходит дерево узлов AST, которые могли бы выглядеть примерно так:
ASSIGN
|
+--+---+
| |
REF MINUS
| |
x +--+---+
| |
VAR PLUS
| |
a +--+--+
| |
VAR CONST
| |
b 3
и эти interpret
функция имела бы дополнительные опции:
int interpret(node) {
switch(node) {
case PLUS:
return interpret(child(0))+interpret(child(1));
case MINUS:
return interpret(child(0))-interpret(child(1));
case ASSIGN:
return set(child(0), interpret(child(1));
case VAR:
return fetch(child(0));
case CONST:
return value(child(0));
...
}
}
при обходе AST с тем interpet
функция (на самом деле выполнение операций) Вы интерпретируете. Но если функция записи действия, которые будут выполнены, а не выполнение их, Вы компилируете. В псевдокоде (на самом деле, псевдокодируйте дважды , поскольку я принимаю гипотетическую стековую машину как цель компиляции):
string compile(node) {
switch(node) {
case PLUS:
return(compile(child(0))) + compile(child(1)) + ADD);
case MINUS:
return(compile(child(0))) + compile(child(1)) + SUB);
case ASSIGN:
return(PUSHA(child(0))) + compile(child(1)) + STORE);
case REF:
return(PUSHA(child(0)));
case VAR:
return(PUSHA(child(0)) + FETCH);
case CONST:
return(PUSHLIT + value(child(0)));
...
}
}
Вызов compile
, на что AST (игнорирующий любые опечатки псевдокода ;-) выложил бы что-то как:
PUSHA x
PUSHA a
FETCH
PUSHA b
FETCH
PUSHLIT 3
ADD
SUB
STORE
FWIW, я был бы склонен думать об этом как о разворачивании AST, вместо того, чтобы разворачивать интерпретатор, но не подвергну критике чью-либо метафору, не читая его в контексте.
Я немного смущен. Я не думаю, 'разворачивая цикл', правильное слово здесь. Даже при рефакторинге кода, чтобы не иметь какие-либо рекурсивные вызовы, Вы будете все еще использовать интерпретатор.
Можно скомпилировать эту программу с GCC. Затем у Вас будет скомпилированная программа, хотя скомпилированная программа будет интерпретировать AST.
Один способ превратить это в компилятор был бы вместо выполнения return interpret(child(0))+interpret(child(1));
, Вы генерировали бы инструкции по сборке, которые сделают дополнение и затем произведут инструкции по сборке в файл.
Фабрика является основанным на стеке языком, не основанным на AST интерпретатором.
я использовал основанные на стеке языки для интерпретаторов агента, таким образом, это - то, как я сделал работу, которая не может быть совершенно непохожей на Фактор.
Каждая функция реализована как функция, которая берет стек и возвращает стек (в моем случае видоизмененная версия того же стека, я не уверен, функционален ли Фактор или видоизменяется). В моих интерпретаторах каждая функция также помещает продолжение функции на вершине стека, таким образом, интерпретатор знает, что сделать затем:
, Таким образом, интерпретатор для вызывания следующей функции на стеке является чем-то как:
for (;;)
stack = (stack[0].function_pointer)(stack);
Рассмотрение функционального нечто:
def foo (x,y):
print( add(x, y) )
добавляют, мог быть определен как:
pop a
pop b
stack[ return_offset ] = a + b
return stack
и нечто как:
pop x
pop y
push _
push &print
push y
push x
push &add
и стек для вызова нечто (5,6) был бы, развивают что-то вроде этого на каждом шаге в цикле:
>> foo(5,6)
[&foo, 5, 6]
[&add, 5, 6, &print, _]
[&print, 11]
=> 11
[]
А простой компилятор мог развернуть цикл для функции нечто, генерируя эквивалентный потоковый код:
compiled_foo (stack):
stack = begin_foo(stack) // arranges stack for call to add
stack = add(stack)
stack = print(stack)
return stack
У Вас действительно нет цикла здесь с тех пор не все вызовы к interpret
последние вызовы.
Компилятор, самый близкий к Вашему, принимая модель стека...
int compile(node)
{
switch(node) {
case PLUS:
return compile(child(0))&&compile(child(1))&&compile_op(op_plus);
case MINUS:
return compile(child(0))&&interpret(child(1))&&compile_op(op_minus);
}
}
но я думаю, разворачивая в этом контексте, более применимо к интерпретатору кода байта, а не интерпретатору AST. Инструкции по байт-коду обычно интерпретируются в цикле. Затем "разворачивающая" техника состоит в том, чтобы испустить код, соответствующий каждой инструкции по байт-коду.
Фактор подобен Forth. Обычно Forth имеет внешний интерпретатор, который генерирует потоковый код. Потоковый код может быть предположен массив указателей функции (существует несколько вариантов, прямой распараллелил, косвенный распараллелил, подпрограмма распараллелила, и т.д.). Потоковый код выполнен внутренним интерпретатором. Разворачивание интерпретатора в этом случае относится к внутреннему интерпретатору и является вопросом конкатенации потокового кода.
Это не может быть связано, но также и проверить вторую проекцию Futamura
http://en.wikipedia.org/wiki/Futamura_projection
, который говорит, что компилятор является просто интерпретатором с частичной оценкой/сворачиванием констант (хороший в теории, но не практике).
В эта статья я прошел пример автоматического преобразования интерпретатора в компилятор (хотя компилируя для Интригования, а не машинный код). Это - те же другие идеи, дали здесь, но Вы могли бы найти полезным видеть, что это автоматизировало.
Интерпретатор сканирует каждый байт-код (или узел AST) во времени выполнения и отправке к вызовам функций (обычно использующий оператор переключения в бесконечном цикле).
Компилятор делает по существу то же самое, но во время компиляции. Компилятор сканирует каждый байт-код (или узел AST) во время компиляции и испускает код (машинный код или некоторый высокоуровневый промежуточный язык как C) для вызывания соответствующей функции во времени выполнения.
Я думаю, что это означает, то, что вместо цикличного выполнения по операторам и выполнения их, Вы циклично выполняетесь по операторам и производите код интерпретатора, который выполнился бы.
В основном, то, что происходит, - то, что код, который выполнился бы в цикле интерпретатора, встраивается в новую функцию. Цикл "развернут", потому что, когда код выполняется, это больше не находится в цикле интерпретатора, это - просто линейный контур через сгенерированную функцию.