Lexing и включить директиву с ocamllex
Я делаю компилятор для C-подобного языка, который должен поддерживать директиву #include (только в начале файла)
Простой, но не элегантный подход заключается в создании подпрограммы, которая находит каждое вхождение директивы и заменяет соответствующий файл в новом временном файле.
Теперь это совсем не приятно. Поэтому я попробовал следующее:
lexer = parse
| "#include \"" ( [^'"' '\n']* as filename) '"'
{ lexer (Lexing.from_channel (open_in filename)) ; lexer lexbuf }
Идея заключалась в следующем: всякий раз, когда вы найдете включение, откройте новый канал, используя заданное имя файла, и рекурсивно вызовите правило "lexer" для этого канала. После этого продолжайте с текущим состоянием вашего lexing-буфера и продолжайте lexing.
Проблема в том, что это никогда не работало.
я также видел, что можно сделать рефиллер, когда буфер lexbuf достиг eof. Но я не смог найти больше информации. Это дало мне идею изменить приведенный выше код следующим образом:
lexer = parse
| "#include \"" ( [^'"' '\n']* as filename) '"'
{ addCurrentLexBufToAStack lexbuf ;lexer (Lexing.from_channel (open_in filename)); }
и в заправке, вы бы продолжили с головы стека
но кажется очень амбициозным для работы.
Есть идеи?
Ps Лексер (и парсинг тоже) вызывается из другого модуля (назовем его Main.ml)
1 ответ
Ну, а вас не смущает лексизм и разбор?
Что я вижу, так это:
Если моя лексема - #include идент, я хочу проанализировать, что находится в файле, указанном идент, и добавить его.
Вы тогда путаете разбор и лексинг
Вы могли бы написать что-то вроде этого: (это небольшая программа, но она работает;-))
ast.mli
type operation =
| Plus of operation * operation
| Minus of operation * operation
| Int of int
type prog = string list * operation list
lexer.mll
{
open Parser
open Lexing
open Ast
let current_pos b =
lexeme_start_p b,
lexeme_end_p b
}
let newline = '\n'
let space = [' ' '\t' '\r']
let digit = ['0' - '9']
let integer = digit+
rule token = parse
| newline { token lexbuf}
| space+ { token lexbuf}
| "#include \"" ( [^'"' '\n']* as filename) '"' { INCLUDE filename }
| integer as i { INTEGER (int_of_string i) }
| "+" { PLUSI }
| "-" { MINUSI }
| ";" { SC }
| "main" { MAIN }
| eof
{ EOF }
parser.mly
%{
open Ast
%}
%token <string> INCLUDE
%token EOF SC
%token PLUSI
%token MINUSI
%token MAIN
%token <int> INTEGER
%left PLUSI MINUSI
%start <Ast.prog> prog
%%
prog:
include_list MAIN operations EOF { ($1, $3) }
include_list:
| { [] }
| INCLUDE include_list { $1 :: $2 }
operation:
| operation PLUSI operation { Plus ($1, $3) }
| operation MINUSI operation { Minus ($1, $3) }
| INTEGER { Int $1 }
operations:
| operation { [$1] }
| operation SC operations { $1 :: $3 }
Итак, как вы можете видеть, когда я анализирую, я помню имена файлов, которые я должен проанализировать и
main.ml
open Lexing
open Ast
let rec print_op fmt op =
match op with
| Plus (op1, op2) ->
Format.fprintf fmt "(%a + %a)"
print_op op1 print_op op2
| Minus (op1, op2) ->
Format.fprintf fmt "(%a - %a)"
print_op op1 print_op op2
| Int i -> Format.fprintf fmt "%d" i
let rec read_includes fl =
List.fold_left (fun acc f ->
let c = open_in f in
let lb = Lexing.from_channel c in
let fl, p = Parser.prog Lexer.token lb in
close_in c;
let acc' = read_includes fl in
acc' @ p
) [] fl
let () =
try
let p = read_includes [Sys.argv.(1)] in
List.iter (Format.eprintf "%a@." print_op) p
with _ -> Format.eprintf "Bad Boy !@."
Это означает, что когда я закончу анализ первого файла, я анализирую включенные файлы.
Самая важная вещь - это ваша путаница с лексингом (что глупее всего в компиляторе, вы просто спрашиваете "Какой следующий токен вы видите?", А он отвечает: "Я вижу"). #include "filename"
"и синтаксический анализатор, который не так глуп, и говорит:" Эй, лексер увидел #include "filename"
поэтому я буду помнить об этом имени файла, потому что оно может понадобиться, и я буду продолжать.
И если у меня есть эти три файла:
file1
#include "file2"
main
6; 7
file2
#include "file3"
main
4; 5
file3
main
1; 2; 3
Если я позвоню ./compile file1
У меня есть выход 1 2 3 4 5 6
что я и хочу;-)
[РЕДАКТИРОВАТЬ]
Новая версия с лексером, обрабатывающим включает в себя:
ast.mli
type operation =
| Plus of operation * operation
| Minus of operation * operation
| Int of int
type prog = operation list
lexer.mll
{
open Parser
let fset = Hashtbl.create 17
(* set keeping all the filenames *)
}
let newline = '\n'
let space = [' ' '\t' '\r']
let digit = ['0' - '9']
let integer = digit+
rule token = parse
| newline { token lexbuf}
| space+ { token lexbuf}
| "#include \"" ( [^'"' '\n']* as filename) '"'
{ if Hashtbl.mem fset filename then
raise Exit
else
let c = open_in filename in
Hashtbl.add fset filename ();
let lb = Lexing.from_channel c in
let p = Parser.prog token lb in
INCLUDE p
}
| integer as i { INTEGER (int_of_string i) }
| "+" { PLUSI }
| "-" { MINUSI }
| ";" { SC }
| "main" { MAIN }
| eof
{ EOF }
parser.mly
%{
open Ast
%}
%token <Ast.prog> INCLUDE
%token EOF SC
%token PLUSI
%token MINUSI
%token MAIN
%token <int> INTEGER
%left PLUSI MINUSI
%start <Ast.prog> prog
%%
prog:
include_list MAIN operations EOF { List.rev_append (List.rev $1) $3 }
include_list:
| { [] }
| INCLUDE include_list { List.rev_append (List.rev $1) $2 }
operation:
| operation PLUSI operation { Plus ($1, $3) }
| operation MINUSI operation { Minus ($1, $3) }
| INTEGER { Int $1 }
operations:
| operation { [$1] }
| operation SC operations { $1 :: $3 }
main.ml
open Lexing
open Ast
let rec print_op fmt op =
match op with
| Plus (op1, op2) ->
Format.fprintf fmt "(%a + %a)"
print_op op1 print_op op2
| Minus (op1, op2) ->
Format.fprintf fmt "(%a - %a)"
print_op op1 print_op op2
| Int i -> Format.fprintf fmt "%d" i
let () =
try
let c = open_in Sys.argv.(1) in
let lb = Lexing.from_channel c in
let p = Parser.prog Lexer.token lb in
close_in c;
List.iter (Format.eprintf "%a@." print_op) p
with _ -> Format.eprintf "Bad Boy !@."
Итак, в лексере, когда я вижу #include filename
Я немедленно вызываю парсер на файл, связанный с filename
и возвращает Ast.prog
разобрали до предыдущего вызова парсинга.
Надеюсь, тебе все ясно;-)
[ВТОРОЕ РЕДАКТИРОВАНИЕ]
Я не могу позволить этот код, как этот, я отредактировал его, чтобы избежать циклов включения (в lexer.mll);-)