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

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