aboutsummaryrefslogtreecommitdiffstats
open Core

let opts = Settings.opts

module Origin = struct
  type t = Filename of string | Stdin | Interactive

  let to_string = function
    | Filename name -> name
    | Stdin -> "<stdin>"
    | Interactive -> "<interactive>"
end

(* [dir] must be an absolute path *)
let rec find_imports_file dir : (string, string) result =
  let def_filename = Filename.concat dir "importdef.sexp" in
  match Core_unix.access def_filename [ `Read ] with
  | Ok () -> Ok def_filename
  | Error (Core_unix.Unix_error (ENOENT, _, _))
  | Error (Core_unix.Unix_error (EACCES, _, _)) ->
      let parent = Filename.dirname dir in
      if String.(parent = dir) then Error "Could not find importdef.sexp file"
      else find_imports_file (Filename.dirname dir)
  | Error _ -> Error "Could not find importdef.sexp file"

let load_imports ~for_ =
  let cwd = Core_unix.getcwd () in
  let filename =
    match !(opts.imports_def_file) with
    | None -> (
        let dir =
          match for_ with
          | Origin.Filename filename ->
              Filename.to_absolute_exn
                (Filename.dirname filename)
                ~relative_to:cwd
          | Origin.Stdin | Origin.Interactive -> cwd
        in
        match find_imports_file dir with
        | Error _ ->
            printf
              "Note: no importdef.sexp was found / could be accessed; imports \
               will not work\n\
               %!";
            None
        | Ok filename ->
            let relative =
              if Filename.is_absolute filename then
                Filename.of_absolute_exn filename ~relative_to:cwd
              else filename
            in
            printf "Imports definition found at %s\n%!" relative;
            Some filename)
    | Some filename -> Some filename
  in
  match filename with
  | None -> Ok []
  | Some filename -> (
      (* User-provided filenames may not be absolute *)
      let filename_abs = Filename.to_absolute_exn filename ~relative_to:cwd in
      try
        Ok
          (In_channel.read_all filename
          |> Sexp.of_string |> Mininix.Sexp.import_forest_of_sexp
          |> Mininix.Import.materialize
               ~relative_to:(Filename.dirname filename_abs))
      with Sys_error err -> Error ("Failed to read imports definition: " ^ err))

let eval_expr_with_imports ~origin ~imports data =
  let cwd = Core_unix.getcwd () in
  let config = Sexp_pretty.Config.default
  and formatter = Stdlib.Format.formatter_of_out_channel stdout in
  try
    if !(opts.print_input) then printf "==> Input Nix:\n%s\n\n%!" data;
    let nexp = Nix.parse ~filename:(Origin.to_string origin) data in
    if !(opts.print_parsed) then (
      print_string "==> Parsed Nix:\n";
      Nix.Printer.print stdout nexp;
      printf "\n\n%!");
    let nnexp =
      Nix.elaborate
        ~dir:
          (Some
             (match origin with
             | Filename name ->
                 Filename.to_absolute_exn ~relative_to:cwd
                   (Filename.dirname name)
             | Stdin | Interactive -> cwd))
        nexp
    in
    if !(opts.print_elaborated) then (
      print_string "==> Parsed, elaborated Nix:\n";
      Nix.Printer.print stdout nnexp;
      printf "\n\n%!");
    if !(opts.print_nix_sexp) then (
      let nsexp = Nix.Ast.sexp_of_expr nnexp in
      print_string "==> Nix S-expr:\n";
      Sexp_pretty.pp_formatter config formatter nsexp;
      printf "\n%!");
    let mnexp = Mininix.Nix2mininix.from_nix nnexp in
    if !(opts.print_mininix_sexp) then (
      let mnsexp = Mininix.Sexp.expr_to_sexp mnexp in
      print_string "==> Mininix S-expr:\n";
      Sexp_pretty.pp_formatter config formatter mnsexp;
      printf "\n%!");
    let mnwpexp = Mininix.apply_prelude mnexp in
    if !(opts.print_mininix_sexp_w_prelude) then (
      let mnwpsexp = Mininix.Sexp.expr_to_sexp mnwpexp in
      print_string "==> Mininix S-expr (+ prelude):\n";
      Sexp_pretty.pp_formatter config formatter mnwpsexp;
      printf "\n%!");
    let res =
      Mininix.interp_tl ~fuel:!(opts.fuel_amount) ~mode:!(opts.eval_strategy)
        ~imports mnwpexp
    in
    if !(opts.print_result_mininix_sexp) then (
      let ressexp = Mininix.Sexp.val_res_to_sexp res in
      print_string "==> Evaluation result (Mininix S-exp):\n";
      Sexp_pretty.pp_formatter config formatter ressexp;
      printf "\n%!");
    match res with
    | Res (Some v) ->
        let nixv = Mininix.Mininix2nix.from_val v in
        if !(opts.print_result_nix_sexp) then (
          let nixvsexp = Nix.Ast.sexp_of_expr nixv in
          print_string "==> Evaluation result (Nix S-exp):\n";
          Sexp_pretty.pp_formatter config formatter nixvsexp;
          printf "\n%!");
        print_string "==> Evaluation result (Nix):\n";
        Nix.Printer.print stdout nixv;
        printf "\n%!";
        true
    | Res None ->
        printf "Failed to evaluate\n%!";
        false
    | _ ->
        printf "Ran out of fuel\n%!";
        false
  with
  | Nix.ParseError msg ->
      printf "Failed to parse: %s\n%!" msg;
      false
  | Nix.ElaborateError msg ->
      printf "Elaboration failed: %s\n%!" msg;
      false
  | Mininix.Nix2mininix.FromNixError msg ->
      printf "Failed to convert Nix to Mininix: %s\n%!" msg;
      false

let eval_expr ~origin data =
  match load_imports ~for_:origin with
  | Ok imports -> eval_expr_with_imports ~origin ~imports data
  | Error msg ->
      print_endline msg;
      false

let eval_ch ~origin ch = In_channel.input_all ch |> eval_expr ~origin

let eval_file filename =
  In_channel.with_file filename ~binary:true
    ~f:(eval_ch ~origin:(Filename filename))

let eval_stdin () = eval_ch In_channel.stdin ~origin:Stdin