Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make copybook extensions configurable #332

Merged
merged 3 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
## [0.1.4] Next release

### Added
- COBOL language configuration for highlighting matching brackets and auto-insertion of line numbers in fixed- format code [#330](https://github.com/OCamlPro/superbol-studio-oss/pull/330)
- Configuration setting for copybook filename extensions [#332](https://github.com/OCamlPro/superbol-studio-oss/pull/332)
- COBOL language configuration for highlighting matching brackets and auto-insertion of line numbers in fixed-format code [#330](https://github.com/OCamlPro/superbol-studio-oss/pull/330)

### Fixed
- Word wrapping in presence of hyphens [#330](https://github.com/OCamlPro/superbol-studio-oss/pull/330)
Expand Down
10 changes: 10 additions & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 72 additions & 43 deletions src/lsp/cobol_common/copybook.ml
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,53 @@ open Ez_file.V1

type fileloc = [ `Word of string | `Alphanum of string ]

type lookup_info =
type lookup_config =
{
libname: string;
libpath: string list;
lookup_path: string list;
lookup_exts: string list;
}

type lookup_error =
{
lookup_libname: string;
lookup_config: lookup_config;
}

(** Filename extensions that we should treat as copybooks and not main
programs. *)
let copybook_extensions = (* this must be a subset of {!libfile_extensions}. *)
[".CPY"; ".cpy";
".CBX"; ".cbx"]
["cpy"; "cbx"]

let libfile_extensions =
[".CPY"; ".CBL"; ".COB"; ".CBX";
".cpy"; ".cbl"; ".cob"; ".cbx"; ""]
["cpy"; "cbl"; "cob"; "cbx"]

let lookup_config ?(libexts = libfile_extensions) libpath =
{
lookup_path = libpath;
lookup_exts = List.map String.lowercase_ascii libexts;
}

type directory = {
dir : string ;
files : string StringMap.t ; (* key is basename in lowercase *)
}
let pp_lookup_config ppf { lookup_path; lookup_exts } =
let pp_path ppf = function
| [] -> Pretty.string ppf "<empty>"
| path -> Pretty.path ppf path
in
Pretty.print ppf
"@[- search@ path:@ %a@]@;\
@[- filename@ extensions:@ %a@]"
pp_path lookup_path Fmt.(list ~sep:sp @@ fmt "%S") lookup_exts

let find_lib ~libpath ?(exts=libfile_extensions) ?fromfile ?libname textname : _ result =
(* --- *)

type directory =
{
dir: string;
files: string StringMap.t; (* key is basename in lowercase *)
}

let find_lib ~lookup_config:({ lookup_path = libpath;
lookup_exts = libexts } as lookup_config)
?fromfile ?libname textname : (string, lookup_error) result =
let libpath = match libname, fromfile with
| None, _ ->
libpath
Expand All @@ -47,49 +72,53 @@ let find_lib ~libpath ?(exts=libfile_extensions) ?fromfile ?libname textname : _
| Some (`Word d | `Alphanum d), _ ->
[d]
in
let libpath_files = List.map (fun dir ->
{ dir ;
files =
let files = try Sys.readdir dir with _ -> [||] in
let map = ref StringMap.empty in
Array.iter (fun file ->
let base = String.lowercase_ascii file in
map := StringMap.add base file !map
) files ;
!map
}
) libpath
let libpath_files =
List.map begin fun dir ->
let files =
let files = try Sys.readdir dir with Sys_error _ -> [||] in
let map = ref StringMap.empty in
Array.iter begin fun file ->
map := StringMap.add (String.lowercase_ascii file) file !map
end files;
!map
in
{ dir; files }
end libpath
in
let exts = List.map String.lowercase_ascii exts in
let try_file libname exts =
let base = String.lowercase_ascii libname in
let without_ext d =
Ok (Filename.concat d.dir @@ StringMap.find base d.files)
and with_ext d ext =
Ok (Filename.concat d.dir @@ StringMap.find (base ^ "." ^ ext) d.files)
in
let rec iter_path path =
match path with
| [] -> Error { libname; libpath }
| d :: path ->
iter_exts d path exts

| [] -> Error { lookup_libname = libname;
lookup_config = { lookup_config with
lookup_path = libpath } }
| d :: path -> iter_exts d path exts
and iter_exts d path exts =
match exts with
| [] -> iter_path path
| ext :: exts ->
let file = base ^ ext in
match StringMap.find file d.files with
| exception Not_found -> iter_exts d path exts
| file -> Ok ( Filename.concat d.dir file )
| [] -> (try without_ext d with Not_found -> iter_path path)
| ext :: exts -> (try with_ext d ext with Not_found -> iter_exts d path exts)
in
iter_path libpath_files
in
(* Note: GnuCOBOL handles copybook names given as string literals or plain
text-words in the same way (except for distinct case-folding, that we don't
handle yet).

TODO: to handle case folding, a copybook name that is given as a text-word
should be put in uppercase unless [lookup_fold=Lower]. *)
match textname with
| `Alphanum w -> (* assume no more filename extension *)
try_file w [""]
| `Alphanum w
| `Word w ->
match try_file w exts with
match try_file w libexts with
| Ok lib -> Ok lib
| Error err -> Error { err with libname = w }
| Error err -> Error { err with lookup_libname = w }

let pp_lookup_error ppf { libname; libpath } =
(* TODO: `note addendum about search path *)
let pp_lookup_error ppf { lookup_libname; lookup_config } =
Pretty.print ppf
"@[Library@ `%s'@ not@ found@ in@ search@ path@ (search@ path:@ @[%a@])@]"
libname Pretty.path libpath
"@[<v>@[Library@ `%s'@ not@ found@]@;%a@]"
lookup_libname pp_lookup_config lookup_config
39 changes: 25 additions & 14 deletions src/lsp/cobol_common/copybook.mli
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,36 @@

type fileloc = [ `Word of string | `Alphanum of string ]

type lookup_info =
type lookup_config =
{
libname: string;
libpath: string list;
lookup_path: string list;
lookup_exts: string list;
}

val pp_lookup_config: lookup_config Pretty.printer

val lookup_config
: ?libexts: string list
-> string list
-> lookup_config

type lookup_error =
{
lookup_libname: string;
lookup_config: lookup_config;
}

val pp_lookup_error: lookup_error Pretty.printer

(* --- *)

val copybook_extensions: string list

val pp_lookup_error: lookup_info Pretty.printer

(** [find_lib ~libpath ?exts ?fromfile ?libname txtname] attempts to locate a file
containing the copybook [txtname], which is a file named [txtname], possibly
appended with an extension from {[".CPY"; ".CBL"; ".COB"; ".CBX"; ".cpy";
".cbl"; ".cob"; ".cbx"]} (considered in order), {e unless} [txtname] is
given as an alphanumeric literal ({i e.g, [txtname = `Alphanum filname] ---
in which case no extension is assumed).
(** [find_lib ~lookup_config ?fromfile ?libname txtname] attempts to locate a
file containing the copybook [txtname], which is a file named [txtname],
possibly appended with an extension from [libexts] (considered in order), {e
unless} [txtname] is given as an alphanumeric literal ({i e.g, [txtname =
`Alphanum filname] --- in which case no extension is assumed).

If [libname] is not provided, then the file is searched within the
directories listed in [libpath] (considered in order). Otherwise, a single
Expand All @@ -42,9 +54,8 @@ val pp_lookup_error: lookup_info Pretty.printer
of the path in the provided order.
*)
val find_lib
: libpath:string list
-> ?exts:string list
: lookup_config: lookup_config
-> ?fromfile:string
-> ?libname:fileloc
-> fileloc
-> (string, lookup_info) result
-> (string, lookup_error) result
3 changes: 2 additions & 1 deletion src/lsp/cobol_lsp/lsp_document.ml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ let rewindable_parse ({ project; textdoc; _ } as doc) =
Cobol_preproc.preprocessor
~options:Cobol_preproc.Options.{
default with
libpath = Lsp_project.libpath_for ~uri:(uri doc) project;
copybook_lookup_config =
Lsp_project.copybook_lookup_config_for ~uri:(uri doc) project;
config = project.config.cobol_config;
source_format = project.config.source_format
} @@
Expand Down
27 changes: 22 additions & 5 deletions src/lsp/cobol_lsp/lsp_project.ml
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ let for_ ~rootdir ~layout =
let in_existing_dir dirname ~layout =
for_ ~rootdir:(Superbol_project.rootdir_at ~dirname) ~layout

let libpath_for ~uri project =
Superbol_project.libpath_for ~filename:(Lsp.Uri.to_path uri) project
let copybook_lookup_config_for ~uri project =
Superbol_project.copybook_lookup_config_for ~filename:(Lsp.Uri.to_path uri)
project

let detect_copybook ~uri project =
Superbol_project.detect_copybook ~filename:(Lsp.Uri.to_path uri) project
Expand Down Expand Up @@ -99,8 +100,9 @@ let update_source_format { config; _ } str : bool =
let update_dialect ({ config; _ } as project) str : bool =
try
let { result; diags } =
Superbol_project.Config.cobol_config_from_dialect_name
~verbose:false str in
Superbol_project.Config.cobol_config_from_dialect_name str
~verbose:false
in
if result = config.cobol_config (* note: structural comparison *)
then false
else begin
Expand All @@ -123,13 +125,24 @@ let update_copybooks: t -> Yojson.Safe.t -> bool = fun { config; _ } json ->
in
try
let libpath = convert_each to_libdir json in
if libpath = config.libpath (* note: structural comparison *)
if libpath = config.libpath (* note: structural comparison *)
then false
else (config.libpath <- libpath; true)
with
Yojson.Safe.Util.(Type_error _ | Undefined _) as e ->
Pretty.invalid_arg "%s: %a" (Yojson.Safe.to_string json) Fmt.exn e

let update_copyexts: t -> Yojson.Safe.t -> bool = fun { config; _ } json ->
let open Yojson.Safe.Util in
try
let libexts = convert_each to_string json in
if libexts = config.libexts (* note: structural comparison *)
then false
else (config.libexts <- libexts; true)
with
Yojson.Safe.Util.(Type_error _ | Undefined _) as e ->
Pretty.invalid_arg "%s: %a" (Yojson.Safe.to_string json) Fmt.exn e

(** [update_project_config assoc project] updates the configuration of [project]
according to key/value paires in [assoc]; returns [true] whenever the
configuration upon termination differs from the configuration upon call. *)
Expand All @@ -155,6 +168,7 @@ let update_project_config assoc project : bool =
"dialect", from_string ~f:update_dialect;
"source-format", from_string ~f:update_source_format;
"copybooks", update_copybooks;
"copyexts", update_copyexts;
]


Expand Down Expand Up @@ -198,6 +212,9 @@ let get_project_config ?(flat = true) project : Yojson.Safe.t =

"copybooks",
`List copybooks;

"copyexts",
`List (List.map (fun s -> `String s) config.libexts);
]
in
if flat
Expand Down
8 changes: 4 additions & 4 deletions src/lsp/cobol_lsp/lsp_project.mli
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ val in_existing_dir: string -> layout:layout -> t
{!Superbol_project.rootdir_for}. *)
val rootdir_for: uri:Lsp.Uri.t -> layout:layout -> rootdir

(** [libpath_for ~uri project] constructs a list of directory names where
copybooks are looked up, for a source file at the given URI, in the given
project. *)
val libpath_for: uri:Lsp.Uri.t -> t -> string list
(** [copybook_lookup_config_for ~uri project] constructs a copybook lookup
configuration for a source file at the given URI, in the given project. *)
val copybook_lookup_config_for
: uri:Lsp.Uri.t -> t -> Cobol_common.Copybook.lookup_config

(** [detect_copybook ~uri project] indicates whether a document at the given URI
for [project] should be treated as a copybook. *)
Expand Down
2 changes: 1 addition & 1 deletion src/lsp/cobol_preproc/preproc_diagnostics.ml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ open Cobol_common.Srcloc.INFIX

type error =
| Copybook_lookup_error of { copyloc: srcloc option;
lnf: Cobol_common.Copybook.lookup_info }
lnf: Cobol_common.Copybook.lookup_error }
| Cyclic_copy of { copyloc: srcloc; filename: string }
| Feature_error of Cobol_config.DIAG.error
| Forbidden of { loc: srcloc; stuff: forbidden_stuff }
Expand Down
Loading
Loading