Директива Lexing и include с помощью ocamllex

Я делаю компилятор для C-подобного языка, который должен поддерживать директиву #include (только в начале файла)

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

Это совсем не хорошо. Итак, я попробовал следующее:

lexer = parse
    | "#include \""   ( [^'"' '\n']* as filename) '"'
    { lexer (Lexing.from_channel (open_in filename)) ; lexer lexbuf }

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

Проблема в том, что это никогда не работало.

Я также видел, что можно сделать пополнение, когда буфер lexbuf достигнет значения eof. Но мне не удалось найти больше информации. Это дало мне идею изменить приведенный выше код на следующий:

lexer = parse
    | "#include \""   ( [^'"' '\n']* as filename) '"'
    { addCurrentLexBufToAStack lexbuf ;lexer (Lexing.from_channel    (open_in filename)); }

а в заправочной машине вы продолжите с головы стека

но, похоже, очень амбициозно работает.

Любые идеи?

P.s. Лексер (и парсинг тоже) вызывается из другого модуля (назовем его Main.ml)


person Cris Tsan    schedule 26.04.2016    source источник


Ответы (1)


Ну, разве вы не запутались в лексировании и синтаксическом анализе?

Я вижу следующее:

Если моя лексема - #include identity, я хочу проанализировать то, что находится в файле, на который указывает identity, и добавить его.

Вы путаете синтаксический анализ и лексирование.

Вы можете написать что-то вроде этого: (это небольшая программа, но она работает ;-))

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

файл2

#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) ;-)

person Lhooq    schedule 26.04.2016
comment
спасибо за интересный ответ (он прояснил мне несколько вещей) и за потраченное время! То, что вы говорите, имеет смысл, но нас проинструктировали (это часть курса) сделать это в лексере, а именно: когда наш лексер находит директиву include, он должен прекратить 'лексирование' текущий файл и продолжить с включенного файла (и он может быть вложенным) - person Cris Tsan; 26.04.2016
comment
Что ж, тогда я работаю над этим, потому что я не хочу работать сегодня! - person Lhooq; 26.04.2016
comment
@Lhooq Разве мы не должны использовать close_in в функции read_includes? - person Anton Trunov; 26.04.2016
comment
@AntonTrunov Да, надо, но я грязный! - person Lhooq; 26.04.2016
comment
@AntonTrunov Но так как я не упрямый, то отредактировал свой код ;-) - person Lhooq; 26.04.2016