Хочется странного, то есть макросов. Для начала — хотя бы самые примитивные, но хотелось бы честные — так что бы потом можно было потом извлечь из этого нечто большее.
По идее, для компилируемого языка макросы должны выглядеть как-то так:
1) В процессе разбора AST мы разбираем макрос — соответственно, у нас должны быть некие токены, которые определяют начало и конец макроса. Ну или хотя бы начало, конец может быть каким-то повторно используемым токеном.
2) Определение макроса может быть, наверное, распарсено тем же самым разборщиком, который разбирает и основной синтаксис.
3) Насколько я себе представляю, результатом разбора определения макроса является построение динамического разборщика
4) В месте, где макрос используется (вероятно, тоже надо отбить какими-то спец. токенами) — поток от начала макры до конца скармливается построенному разборщику, интерпретируется и результат интепретации применяется к некоему узлу AST.
Правильно ли это понимание, и есть ли какие-то более простые подходы? Серьезная засада мне тут видится с тем, что, например, сейчас очень многие проверки — вплоть до типизации некоторых моментов — делаются в парсере, совсем декларативно. Возможность делать произвольные преобразования над AST приведет к дополнительному анализу уже после раскрытия макросов, что может быть нетривиально, и вообще время компиляции никак не сократит.
Конкретно сейчас, хочется научиться определять именованные константы времени компиляции — но по идее, любых типов — так что, что бы делать константные, например, туплы — уже нужны честные макросы.
Где бы посмотреть? Кроме немерле есть что нибудь статическое, вменяемое и с макрами (не надо только про с++) ?
dmz>Хочется странного, то есть макросов. Для начала — хотя бы самые примитивные, но хотелось бы честные — так что бы потом можно было потом извлечь из этого нечто большее.
dmz>По идее, для компилируемого языка макросы должны выглядеть как-то так: Composable and Compilable Macros
Лично мне последнее время плагинные расширители компиляторов нравятся больше.
Совершенно готовая к работе система с демонстрационным расширением языка С 22-мя плагинами. Xoc, an Extension-Oriented Compiler.
Соответственный диссер: An Extension-Oriented Compiler.
Здравствуйте, dmz, Вы писали:
dmz>2) Определение макроса может быть, наверное, распарсено тем же самым разборщиком, который разбирает и основной синтаксис. dmz>3) Насколько я себе представляю, результатом разбора определения макроса является построение динамического разборщика
Тут проще, основной разборщик должен быть расширяемым, тогда и динамический разборщик не нужен.
dmz>4) В месте, где макрос используется (вероятно, тоже надо отбить какими-то спец. токенами) — поток от начала макры до конца скармливается построенному разборщику, интерпретируется и результат интепретации применяется к некоему узлу AST.
Вы хотите многостадийную компиляцию сделать? Тогда смотрите на Meta ML, ИМХО, это потом выливается в жуткий геморрой.
dmz>Правильно ли это понимание, и есть ли какие-то более простые подходы? Серьезная засада мне тут видится с тем, что, например, сейчас очень многие проверки — вплоть до типизации некоторых моментов — делаются в парсере, совсем декларативно. Возможность делать произвольные преобразования над AST приведет к дополнительному анализу уже после раскрытия макросов, что может быть нетривиально, и вообще время компиляции никак не сократит.
Именно! Это и называется многостадийная компиляция со всеми вытекающими минусами.
dmz>Где бы посмотреть? Кроме немерле есть что нибудь статическое, вменяемое и с макрами (не надо только про с++) ?
Я как пример, смотрел на Meta ML, Template Haskell.
Lisp is not dead. It’s just the URL that has changed: http://clojure.org
Y>Тут проще, основной разборщик должен быть расширяемым, тогда и динамический разборщик не нужен.
Это мне придется выкинуть ocamlyacc. Наверное, я не готов прямо сейчас на это пойти. К тому же, я не уверен, что camlp4, который я вижу как замену, может динамически строить правила, надо еще посмотреть.
dmz>>4) В месте, где макрос используется (вероятно, тоже надо отбить какими-то спец. токенами) — поток от начала макры до конца скармливается построенному разборщику, интерпретируется и результат интепретации применяется к некоему узлу AST.
Y>Вы хотите многостадийную компиляцию сделать? Тогда смотрите на Meta ML, ИМХО, это потом выливается в жуткий геморрой.
Не, хочется делать в один проход как раз — вроде как раз получается, если парсер, наткнувшись на некий макрос, возвращает результат его разбора другим парсером. Второй вариант — сильно ограничить возможности макросов, так, что бы их раскрытие было заведомо корректное, а возможные макросы описывались бы правилами того-же самого парсера N1, но приводили бы не созданию самой конструкции, а обернутой — т.е. при определении макроса происходило бы не
X -> Y
а
X -> MacroDef(Y)
а при использовании — подставлялся бы из словаря, соответственно, Y. В принципе, если ограничить множество возможных X, а так же, контексты, где возможна макроподстановка, то можно обойтись малой кровью, наверное.
Y>Именно! Это и называется многостадийная компиляция со всеми вытекающими минусами.
Да уж. Собственно, сейчас хочется сделать не в полном объеме, а просто наметить, что бы потом когда-нибудь сделать в полном объеме. Ну и не хочется конкструкции языка, которые выражаются через уже реализованные конструкции, делать отдельно.
Y>Я как пример, смотрел на Meta ML, Template Haskell.
dmz>а при использовании — подставлялся бы из словаря, соответственно, Y. В принципе, если ограничить множество возможных X, а так же, контексты, где возможна макроподстановка, то можно обойтись малой кровью, наверное.
Ну, в общем, прямо при парсинге раскрывать макры, естесственно, не получится, так как спуск рекурсивный, придется раскрывать на уровне AST чем-то вроде унификации. Но унификация, в общем, работает достаточно быстро. Ну или скорость ее работы падает достаточно медленно. В общем, если принять за аксиому то, что макросы могут раскрыться только в элемент AST (а можно было сразу в код, например — так как есть statement EMIT и в принципе ничего не мешает раскрываться туда, но опасно) — а они бывают, грубо, либо Statement либо Expression, и если сделать макры двух видов — MacroStmt и MaxroExpr — то их раскрытие должно происходить ровно в тех контекстах, в которых может быть Expression или Statement. Таким образом, никаких дополнительных проверок не надо, и получается что-то вроде следующего:
@literal zabor "ZABOR";
@literal golova "GOLOVA";
@literal thirty_five 35;
def main()
{
local eq = `zabor == `golova;
local eq2 = `zabor == "GOLOVA";
local a = `thirty_five;
local moo = a == `zabor; #BDYSH...
}
и выливается примерно в такое:
let rec with_funcs f l = match l with
| (FuncDef(fp,c))::ds -> (f (FuncDef(fp,c))) :: (with_funcs f ds)
| d::ds -> d :: (with_funcs f ds)
| [] -> []
let expand_macros (Module({mod_defs=defs},c)) =
let ql = List.map (function MacroDef(MacroLiteral(name,e),c) -> (name,e)::[] | _ -> [])
in let equots = defs |> ql |> List.fold_left (@) []
in let rec exp_macro_expr e = match e with
| ENothing -> ENothing
| ELiteral(n,c) -> (ELiteral(n,c))
| EListNil(c) -> (EListNil(c))
| EIdent(n,c) -> (EIdent(n,c))
| EVoid(ECall((e,a),c)) -> (EVoid(ECall((exp_macro_expr e, List.map exp_macro_expr a),c)))
| ECall((e,a),c) -> (ECall((exp_macro_expr e, List.map exp_macro_expr a),c))
| EVoid(x) -> (EVoid(x))
| ERecord((n,ef),c) -> (ERecord((n,List.map exp_macro_expr ef),c))
| ERecordFieldInit((n,e),c)-> (ERecordFieldInit((n,exp_macro_expr e),c))
| ERecordField(Rec(e),x) -> (ERecordField(Rec(exp_macro_expr e),x))
| EBoolUn(op, e, c) -> (EBoolUn(op, exp_macro_expr e, c))
| EAriphUn(op, e,c) -> (EAriphUn(op, exp_macro_expr e,c))
| EPair( (e1, e2), c ) -> (EPair( (exp_macro_expr e1, exp_macro_expr e2), c ))
| EList( (e1, e2), c ) -> (EList( (exp_macro_expr e1, exp_macro_expr e2), c ))
| ECmp(op, (e1,e2), c ) -> (ECmp(op, (exp_macro_expr e1, exp_macro_expr e2), c ))
| EBoolBin(op, (e1,e2), c) -> (EBoolBin(op, (exp_macro_expr e1, exp_macro_expr e2), c))
| EAriphBin(op, (e1,e2),c) -> (EAriphBin(op, (exp_macro_expr e1, exp_macro_expr e2),c))
| EQuot(n,c) -> List.assoc n equots
in let exp_macro_stmt stmt = match stmt with
| StArg _ | StLocal _
| StEmpty _ | StBreak _
| StContinue _ | StIf _
| StEmit _
| StBranchElse _ -> stmt
| StAssign(x,e) -> StAssign(x, exp_macro_expr e)
| StWhile((e,x),c) -> StWhile((exp_macro_expr e,x),c)
| StBranch((e,x),c) -> StBranch((exp_macro_expr e,x),c)
| StCall(e,c) -> StCall(exp_macro_expr e,c)
| StRet(e,c) -> StRet(exp_macro_expr e,c)
in let exp_macro_code (Block({blk_code=code},c)) =
Block({blk_code=List.map exp_macro_stmt code},c)
in let exp_macro_fun fn = match fn with
| FuncDef(fp,c) -> FuncDef({fp with func_code=exp_macro_code fp.func_code},c)
| _ -> assert false
in Module({mod_defs=with_funcs exp_macro_fun defs},c)
Слушай, если хочешь, могу потом из дома выслать тебе на мыло материалы, которыми я пользовался (если найду конечно, довольно много времени прошло), там были какие-то работы по многостадийной компиляции и по макросам. А то я на самом деле много чего подзабыл, все помню смутно, могу много глупостей наговорить
Lisp is not dead. It’s just the URL that has changed: http://clojure.org
Y>Слушай, если хочешь, могу потом из дома выслать тебе на мыло материалы, которыми я пользовался (если найду конечно, довольно много времени прошло), там были какие-то работы по многостадийной компиляции и по макросам. А то я на самом деле много чего подзабыл, все помню смутно, могу много глупостей наговорить
Да на самом деле, вроде все несложно получается.
1) При определении макроса мы генерируем нечто, что может применяться к узлу или узлам AST
2) При раскрытии — таки да, применяем это к узлам и получаем раскрытый макрос
3) Если при описании макроса можно пользоваться только языковыми конструкциями, а не произвольными — то нам достаточно
иметь парсер, который может парсить поток по частям — т.е. просто есть способ отпарсить часть потока. Пока насколько я вижу, вполне хватает комбинатора парсеров. Если бы его взял — вот прямо сейчас бы мог сделать очень широкий класс макросов. А так не знаю, получится или нет. Впрочем, возможно что и ocamlyacc умеет парсить не от top_level, но это надо разбираться.
Насчет прислать — давай, если не жалко, посмотрю когда-нибудь.