pax_global_header00006660000000000000000000000064146164733610014525gustar00rootroot0000000000000052 comment=4fa41f7601a60bd5b751565c14e38ae8480aab79 ppx_string-0.17.0/000077500000000000000000000000001461647336100140075ustar00rootroot00000000000000ppx_string-0.17.0/.gitignore000066400000000000000000000000411461647336100157720ustar00rootroot00000000000000_build *.install *.merlin _opam ppx_string-0.17.0/.ocamlformat000066400000000000000000000000231461647336100163070ustar00rootroot00000000000000profile=janestreet ppx_string-0.17.0/CHANGES.md000066400000000000000000000001301461647336100153730ustar00rootroot00000000000000## Release v0.17.0 * Refactor codebase to support more general kinds of interpolation. ppx_string-0.17.0/CONTRIBUTING.md000066400000000000000000000044101461647336100162370ustar00rootroot00000000000000This repository contains open source software that is developed and maintained by [Jane Street][js]. Contributions to this project are welcome and should be submitted via GitHub pull requests. Signing contributions --------------------- We require that you sign your contributions. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org][dco]): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: ``` Signed-off-by: Joe Smith ``` Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with git commit -s. [dco]: http://developercertificate.org/ [js]: https://opensource.janestreet.com/ ppx_string-0.17.0/LICENSE.md000066400000000000000000000021461461647336100154160ustar00rootroot00000000000000The MIT License Copyright (c) 2020--2024 Jane Street Group, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ppx_string-0.17.0/Makefile000066400000000000000000000004031461647336100154440ustar00rootroot00000000000000INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),) default: dune build install: dune install $(INSTALL_ARGS) uninstall: dune uninstall $(INSTALL_ARGS) reinstall: uninstall install clean: dune clean .PHONY: default install uninstall reinstall clean ppx_string-0.17.0/README.org000066400000000000000000000041171461647336100154600ustar00rootroot00000000000000 This extension provides a syntax for string interpolation. Here is an example of its features: #+begin_src ocaml let script_remotely (user : string) (host : string) (port : int) (script : string) = [%string "ssh %{user}@%{host} -p %{port#Int} %{Sys.quote script}"] #+end_src The above expression is equivalent to: #+begin_src ocaml let script_remotely (user : string) (host : string) (port : int) (script : string) = String.concat "" [ "ssh " ; user ; "@" ; host ; " -p " ; Int.to_string port ; " " ; Sys.quote script ] #+end_src Compared to =Printf.sprintf=: #+begin_src ocaml let script_remotely (user : string) (host : string) (port : int) (script : string) = sprintf "ssh %s@%s -p %d %s" user host port (Sys.quote script) #+end_src having the values inline instead of after the format string can make it easier to understand the resulting string, and avoids the potential mistake of passing arguments in the wrong order. This is truer the more format arguments there are. On the other hand, some things are much easier with printf: pad numbers with zeroes, pad strings on the right, display floats in a specific formats, etc. Compared to manually writing something like =String.concat= version above, ppx_string is shorter and can oftentimes be less error-prone (it's really easy to forget whitespace after =ssh= or around =-p= in the explicit =String.concat= version). To emit the literal sequence =%{=, you can escape it as follows: #+begin_src ocaml [%string {|%{"%{"}|}] #+end_src To pad strings with spaces on the left, add an integer expression after a colon: #+begin_src ocaml [%string "%{col1#Int:term_width / 2}%{col2#:term_width/4}%{col3#:8}%{col4}"] #+end_src is equivalent to: #+begin_src ocaml let pad str len = let pad_len = max 0 (len - String.length str) in let padding = String.make pad_len ' ' in padding ^ str in String.concat "" [ pad (Int.to_string col1) (term_width / 2) ; pad col2 (term_width / 4) ; pad col3 8 ; col4 ] #+end_src (note that the pad length can be dynamic, as with the format string "%*s") ppx_string-0.17.0/dune000066400000000000000000000000001461647336100146530ustar00rootroot00000000000000ppx_string-0.17.0/dune-project000066400000000000000000000000211461647336100163220ustar00rootroot00000000000000(lang dune 3.11) ppx_string-0.17.0/ppx_string.opam000066400000000000000000000014011461647336100170560ustar00rootroot00000000000000opam-version: "2.0" version: "v0.17.0" maintainer: "Jane Street developers" authors: ["Jane Street Group, LLC"] homepage: "https://github.com/janestreet/ppx_string" bug-reports: "https://github.com/janestreet/ppx_string/issues" dev-repo: "git+https://github.com/janestreet/ppx_string.git" doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/ppx_string/index.html" license: "MIT" build: [ ["dune" "build" "-p" name "-j" jobs] ] depends: [ "ocaml" {>= "5.1.0"} "base" {>= "v0.17" & < "v0.18"} "ppx_base" {>= "v0.17" & < "v0.18"} "dune" {>= "3.11.0"} "ppxlib" {>= "0.28.0"} ] available: arch != "arm32" & arch != "x86_32" synopsis: "Ppx extension for string interpolation" description: " Part of the Jane Street's PPX rewriters collection. " ppx_string-0.17.0/runtime/000077500000000000000000000000001461647336100154725ustar00rootroot00000000000000ppx_string-0.17.0/runtime/dune000066400000000000000000000001631461647336100163500ustar00rootroot00000000000000(library (name ppx_string_runtime) (public_name ppx_string.runtime) (libraries) (preprocess no_preprocessing)) ppx_string-0.17.0/runtime/ppx_string_runtime.ml000066400000000000000000000005701461647336100217660ustar00rootroot00000000000000open Stdlib open StdLabels module type S = Ppx_string_runtime_intf.S module For_string = struct let empty = "" let of_string t = t let convert t = t let concat list = String.concat ~sep:"" list let pad t ~len = let n = String.length t in if n >= len then t else String.make (len - n) ' ' ^ t ;; external identity : string -> string = "%identity" end ppx_string-0.17.0/runtime/ppx_string_runtime.mli000066400000000000000000000000631461647336100221340ustar00rootroot00000000000000include Ppx_string_runtime_intf.Ppx_string_runtime ppx_string-0.17.0/runtime/ppx_string_runtime_intf.ml000066400000000000000000000027211461647336100230060ustar00rootroot00000000000000(** Signature for runtime implementations of Ppx_string's backend. May be used for derived ppxes using different types or modified behavior. Types [t], [conversion], and [length] should be erased using destructive substitution, i.e. [:=]. Otherwise they introduce new aliases for the types in question, and error messages or Merlin may start referring to them. *) module type S = sig (** Result type of interpolation, and of interpolated %{values}. *) type t (** Result type of %{converted#String} interpolated values. This will often be either [string] or [t], depending on what is convenient for the configured ppx. *) type conversion (** Type of length values for %{padding#:8}. *) type length (** Empty string. *) val empty : t (** Literal string. *) val of_string : string -> t (** Finish a conversion to [t]. *) val convert : conversion -> t (** Combine multiple values in order. *) val concat : t list -> t (** Pad to some minimum length. *) val pad : t -> len:length -> t (** Identity function. Used for ensuring an argument has type [t] in expanded code, without needing the type [t] to be exported explicitly for a type annotation. See note above about destructive substitution. *) external identity : t -> t = "%identity" end module type Ppx_string_runtime = sig module type S = S module For_string : S with type t := string and type length := int and type conversion := string end ppx_string-0.17.0/src/000077500000000000000000000000001461647336100145765ustar00rootroot00000000000000ppx_string-0.17.0/src/dune000066400000000000000000000003451461647336100154560ustar00rootroot00000000000000(library (name ppx_string) (public_name ppx_string) (kind ppx_rewriter) (libraries base compiler-libs.common ppxlib) (ppx_runtime_libraries ppx_string.runtime) (preprocess (pps ppx_base ppxlib.metaquot ppxlib.traverse))) ppx_string-0.17.0/src/ppx_string.ml000066400000000000000000000202361461647336100173300ustar00rootroot00000000000000open Base open Ppxlib open Ast_builder.Default include Ppx_string_intf.Definitions module Where = struct type t = | Imprecise of Location.t | Precise of { mutable position : position } let is_precise = function | Imprecise _ -> false | Precise _ -> true ;; let advance position char = let pos_cnum = position.pos_cnum + 1 in match char with | '\n' -> { position with pos_lnum = position.pos_lnum + 1; pos_bol = pos_cnum; pos_cnum } | _ -> { position with pos_cnum } ;; let skip t string = match t with | Imprecise _ -> () | Precise at -> for pos = 0 to String.length string - 1 do at.position <- advance at.position string.[pos] done ;; let loc_start = function | Imprecise loc -> loc.loc_start | Precise { position } -> position ;; let loc_end = function | Imprecise loc -> loc.loc_end | Precise { position } -> position ;; let skip_with_loc t string = let loc_start = loc_start t in skip t string; let loc_end = loc_end t in { loc_ghost = true; loc_start; loc_end } ;; let has_escapes ~loc ~string ~delimiter = match delimiter with | Some _ -> false | None -> let unescaped_len = 1 + String.length string + 1 in let actual_len = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum in unescaped_len <> actual_len ;; let literal_prefix ~delimiter = match delimiter with | None -> "\"" | Some id -> Printf.sprintf "{%s|" id ;; let create ~loc ~string ~delimiter ~preprocess_before_parsing = if Option.is_some preprocess_before_parsing || has_escapes ~loc ~string ~delimiter then Imprecise { loc with loc_ghost = true } else ( let t = Precise { position = loc.loc_start } in skip t (literal_prefix ~delimiter); t) ;; end let dot id name = pexp_ident ~loc:id.loc { id with txt = Ldot (id.txt, name) } let config_expr ~(config : Config.t) ~loc name = dot { loc; txt = config.fully_qualified_runtime_module } name ;; let interpret ~(config : Config.t) ({ loc_start; value; module_path; pad_length; loc_end; interpreted_string = _ } : Part.Interpreted.t) = let loc = { loc_ghost = true; loc_start; loc_end } in let unpadded = match module_path with | None -> value | Some fn -> [%expr [%e config_expr ~config ~loc "convert"] ([%e dot fn config.conversion_function_name] [%e value])] in match pad_length with | None -> unpadded | Some len -> [%expr [%e config_expr ~config ~loc "pad"] [%e unpadded] ~len:[%e len]] ;; let parse_literal string ~where ~start ~until ~acc = if start >= until then acc else ( let literal = String.sub string ~pos:start ~len:(until - start) in let loc = Where.skip_with_loc where literal in Part.Literal { txt = literal; loc } :: acc) ;; let set_locs loc = object inherit Ast_traverse.map method! location _ = loc end ;; let parse_error ~loc ~name string = Location.raise_errorf ~loc "invalid %s: %S" name string ;; let parse_expression ~where ~loc ~name string = let lexbuf = Lexing.from_string string in lexbuf.lex_abs_pos <- loc.loc_start.pos_cnum; lexbuf.lex_curr_p <- loc.loc_start; match Parse.expression lexbuf with | exception _ -> parse_error ~loc ~name string | expr -> if Where.is_precise where then expr else (set_locs loc)#expression expr ;; let parse_ident ~where ~loc ~name module_path = match parse_expression ~where ~loc ~name module_path with | { pexp_desc = Pexp_construct (ident, None); _ } -> ident | _ -> parse_error ~loc ~name module_path ;; let parse_body ~where string = let loc = Where.skip_with_loc where string in parse_expression ~where ~loc ~name:"%{...} expression" string ;; let parse_module_path ~where string = let loc = Where.skip_with_loc where string in parse_ident ~where ~loc ~name:"%{...} module path" string ;; let parse_pad_length ~where string = let loc = Where.skip_with_loc where string in parse_expression ~where ~loc ~name:"%{...} pad length" string ;; let parse_interpreted string ~where ~start ~until ~acc = Where.skip where "%{"; let loc_start = Where.loc_start where in let string = String.sub string ~pos:start ~len:(until - start) in let value, module_path, pad_length = match String.rsplit2 string ~on:'#' with | None -> let value = parse_body ~where string in value, None, None | Some (body, formatting) -> let body = parse_body ~where body in Where.skip where "#"; let module_path, pad_length = match String.rsplit2 formatting ~on:':' with | None -> let fn = parse_module_path ~where formatting in Some fn, None | Some (module_path, pad_length) -> let fn = if String.is_empty module_path then None else Some (parse_module_path ~where module_path) in Where.skip where ":"; let len = parse_pad_length ~where pad_length in fn, Some len in body, module_path, pad_length in let loc_end = Where.loc_end where in Where.skip where "}"; Part.Interpreted { loc_start; value; module_path; pad_length; loc_end; interpreted_string = string } :: acc ;; type interpreted = { percent : int ; lbrace : int ; rbrace : int } let find_interpreted string ~where ~pos = String.substr_index string ~pos ~pattern:"%{" |> Option.map ~f:(fun percent -> let lbrace = percent + 1 in match String.substr_index string ~pos:(lbrace + 1) ~pattern:"}" with | None -> Where.skip where (String.sub string ~pos ~len:(percent - pos)); let loc = Where.skip_with_loc where "%{" in Location.raise_errorf ~loc "unterminated %%{" | Some rbrace -> { percent; lbrace; rbrace }) ;; let rec parse_from string ~where ~pos ~acc = match find_interpreted string ~where ~pos with | None -> let len = String.length string in let acc = parse_literal string ~where ~start:pos ~until:len ~acc in List.rev acc | Some { percent; lbrace; rbrace } -> let acc = parse_literal string ~where ~start:pos ~until:percent ~acc in let acc = parse_interpreted string ~where ~start:(lbrace + 1) ~until:rbrace ~acc in parse_from string ~where ~pos:(rbrace + 1) ~acc ;; let parse ~(config : Config.t) ~string_loc ~delimiter string = let preprocess_before_parsing = config.preprocess_before_parsing in let string = match preprocess_before_parsing with | None -> string | Some preprocess -> preprocess string in let where = Where.create ~loc:string_loc ~delimiter ~string ~preprocess_before_parsing in let parts = parse_from string ~where ~pos:0 ~acc:[] in let locations_are_precise = Where.is_precise where in ({ parts; locations_are_precise } : Parse_result.t) ;; let expand_part_to_expression ~config part = match (part : Part.t) with | Literal { txt; loc } -> [%expr [%e config_expr ~config ~loc "of_string"] [%e estring txt ~loc]] | Interpreted interpreted -> interpret ~config interpreted ;; let concatenate ~config ~loc expressions = match expressions with | [] -> [%expr [%e config_expr ~config ~loc "empty"]] | [ expr ] -> [%expr [%e config_expr ~config ~loc "identity"] [%e expr]] | multiple -> [%expr [%e config_expr ~config ~loc "concat"] [%e elist ~loc multiple]] ;; let expand ~config ~expr_loc ~string_loc ~string ~delimiter = (parse ~config ~string_loc ~delimiter string).parts |> List.map ~f:(expand_part_to_expression ~config) |> concatenate ~config ~loc:expr_loc ;; let extension ~name ~(config : Config.t) = Extension.declare name Extension.Context.expression Ast_pattern.(pstr (pstr_eval (pexp_constant (pconst_string __' __ __)) nil ^:: nil)) (fun ~loc:expr_loc ~path:_ { loc = string_loc; txt = string } _ delimiter -> Merlin_helpers.hide_expression (expand ~config ~expr_loc ~string_loc ~string ~delimiter)) ;; let (config_for_string : Config.t) = { fully_qualified_runtime_module = Ldot (Lident "Ppx_string_runtime", "For_string") ; conversion_function_name = "to_string" ; preprocess_before_parsing = None } ;; let () = Ppxlib.Driver.register_transformation "ppx_string" ~extensions:[ extension ~name:"ppx_string.string" ~config:config_for_string ] ;; ppx_string-0.17.0/src/ppx_string.mli000066400000000000000000000000431461647336100174730ustar00rootroot00000000000000include Ppx_string_intf.Ppx_string ppx_string-0.17.0/src/ppx_string_intf.ml000066400000000000000000000050701461647336100203470ustar00rootroot00000000000000open! Base open Ppxlib module Definitions = struct (** Used to configure different instances of this ppx. May be used, for example, to add preprocessing, or to interpolate a different string-like type. *) module Config = struct type t = { fully_qualified_runtime_module : Longident.t (** Where to find an implementation of [Ppx_string_runtime.S]. The implementation of [[%string]] is at [Ldot (Lident "Ppx_string_runtime", "For_string")] *) ; conversion_function_name : string (** Conversion function implied by ["%{expr#Module}"], e.g. ["to_string"]. *) ; preprocess_before_parsing : (string -> string) option (** Preprocessing to apply before parsing the string for interpolation. If [None], source locations can be computed precisely based on the result of parsing. *) } end module Part = struct module Interpreted = struct type t = { loc_start : position ; value : expression ; module_path : longident_loc option ; pad_length : expression option ; loc_end : position ; interpreted_string : string (** [interpreted_string] is the string of the interpreted part. (e.g. in the example %{foo#Foo}, the string is "foo#Foo") *) } end type t = | Literal of string loc | Interpreted of Interpreted.t end module Parse_result = struct type t = { parts : Part.t list ; locations_are_precise : bool } end end module type Ppx_string = sig include module type of struct include Definitions end (** Parse a string to find interpolated substrings. *) val parse : config:Config.t -> string_loc:location -> delimiter:string option -> string -> Parse_result.t (** Interpret an interpolated string as an expression, including %{conversions#String} and %{padding#:8}. *) val interpret : config:Config.t -> Part.Interpreted.t -> expression (** Combines [parse], [interpret], and concatenation to expand an interpolated string to an expression implementing it. *) val expand : config:Config.t -> expr_loc:location -> string_loc:location -> string:string -> delimiter:string option -> expression (** Construct an [Extension.t] implementing the configured interpolation ppx. *) val extension : name:string -> config:Config.t -> Extension.t (** Configuration for [[%string]]: string type and conversion type are [string], length type is [int], and no preprocessing. *) val config_for_string : Config.t end