This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1 @@
* @diml

4
unikernel/duniverse/csexp/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
_opam
_build
*.install
.merlin

View file

@ -0,0 +1,12 @@
version=0.24.1
profile=conventional
ocaml-version=4.08.0
break-separators=before
dock-collection-brackets=false
doc-comments=before
let-and=sparse
type-decl=sparse
cases-exp-indent=2
break-cases=fit-or-vertical
parse-docstrings=true
module-item-spacing=sparse

View file

@ -0,0 +1,64 @@
# 1.5.2
- Fix `Csexp.serialised_length`. Previously, it would under count by 2 because
it did not take the parentheses into account. (#22, @jchavarri)
# 1.5.1
- Drop dependency on result and compatibility with OCaml 4.02 (#17,
@rgrinberg)
# 1.5.0
Replaced by 1.5.1 because of accidentally breaking compat with 4.03.
# 1.4.0
- Add a `Csexp.t` type and extend `Csexp` to include the module from the functor
application (#14, @rgrinberg)
# 1.3.2
- The project now builds with dune 1.11.0 and onward (#12, @voodoos)
# 1.3.1
- Fix compatibility with 4.02.3
# 1.3.0
- Add a "feed" API for parsing. This new API let the user feed
characters one by one to the parser. It gives more control to the
user and the handling of IO errors is simpler and more
explicit. Finally, it allocates less (#9, @jeremiedimino)
- Fixes `input_opt`; it was could never return [None] (#9, fixes #7,
@jeremiedimino)
- Fixes `parse_many`; it was returning s-expressions in the wrong
order (#10, @rgrinberg)
# 1.2.3
- Fix `parse_string_many`; it used to fail on all inputs (#6, @rgrinberg)
# 1.2.2
- Fix compatibility with 4.02.3
# 1.2.1
- Remove inclusion of the `Result` module, which was accidentally
added in a previous PR. (#3, @rgrinberg)
# 1.2.0
- Expose low level, monad agnostic parser. (#2, @mefyl)
# 1.1.0
- Add compatibility up-to OCaml 4.02.3 (with disabled tests). (#1, @voodoos)
# 1.0.0
- Initial release

View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2016 Jane Street Group, LLC <opensource@janestreet.com>
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.

View file

@ -0,0 +1,31 @@
INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),)
default:
dune runtest
test:
dune runtest
install:
dune install $(INSTALL_ARGS)
uninstall:
dune uninstall $(INSTALL_ARGS)
reinstall: uninstall install
clean:
dune clean
all-supported-ocaml-versions:
dune build @install @runtest --workspace dune-workspace.dev
dune-release:
dune-release tag
dune-release distrib --skip-build --skip-lint --skip-tests
# See https://github.com/ocamllabs/dune-release/issues/206
DUNE_RELEASE_DELEGATE=github-dune-release-delegate dune-release publish distrib --verbose
dune-release opam pkg
dune-release opam submit
.PHONY: default install uninstall reinstall clean test

View file

@ -0,0 +1,33 @@
Csexp - Canonical S-expressions
===============================
This project provides minimal support for parsing and printing
[S-expressions in canonical form][wikipedia], which is a very simple
and canonical binary encoding of S-expressions.
[wikipedia]: https://en.wikipedia.org/wiki/Canonical_S-expressions
Example
-------
```ocaml
# #require "csexp";;
# module Sexp = struct type t = Atom of string | List of t list end;;
module Sexp : sig type t = Atom of string | List of t list end
# module Csexp = Csexp.Make(Sexp);;
module Csexp :
sig
val parse_string : string -> (Sexp.t, int * string) result
val parse_string_many : string -> (Sexp.t list, int * string) result
val input : in_channel -> (Sexp.t, string) result
val input_opt : in_channel -> (Sexp.t option, string) result
val input_many : in_channel -> (Sexp.t list, string) result
val serialised_length : Sexp.t -> int
val to_string : Sexp.t -> string
val to_buffer : Buffer.t -> Sexp.t -> unit
val to_channel : out_channel -> Sexp.t -> unit
end
# Csexp.to_string (List [ Atom "Hello"; Atom "world!" ]);;
- : string = "(5:Hello6:world!)"
```

View file

@ -0,0 +1,21 @@
open StdLabels
module Sexp = struct
type t =
| Atom of string
| List of t list
end
module Csexp = Csexp.Make (Sexp)
let atom = Sexp.Atom (String.make 128 'x')
let rec gen_sexp depth =
if depth = 0 then atom
else
let x = gen_sexp (depth - 1) in
List [ x; x ]
let s = Sys.opaque_identity (Csexp.to_string (gen_sexp 16))
let%bench "of_string" = ignore (Csexp.parse_string s : _ result)

View file

@ -0,0 +1,12 @@
(library
(name csexp_bench)
(libraries csexp)
(library_flags -linkall)
(preprocess
(pps ppx_bench))
(modules csexp_bench))
(executable
(name main)
(modules main)
(libraries core_bench.inline_benchmarks csexp_bench))

View file

@ -0,0 +1 @@
let () = Inline_benchmarks_public.Runner.main ~libname:"csexp_bench"

View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
export BENCHMARKS_RUNNER=TRUE
export BENCH_LIB=csexp_bench
exec dune exec -- ./main.exe -fork -run-without-cross-library-inlining "$@"

View file

@ -0,0 +1,51 @@
version: "1.5.2"
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Parsing and printing of S-expressions in Canonical form"
description: """
This library provides minimal support for Canonical S-expressions
[1]. Canonical S-expressions are a binary encoding of S-expressions
that is super simple and well suited for communication between
programs.
This library only provides a few helpers for simple applications. If
you need more advanced support, such as parsing from more fancy input
sources, you should consider copying the code of this library given
how simple parsing S-expressions in canonical form is.
To avoid a dependency on a particular S-expression library, the only
module of this library is parameterised by the type of S-expressions.
[1] https://en.wikipedia.org/wiki/Canonical_S-expressions
"""
maintainer: ["Jeremie Dimino <jeremie@dimino.org>"]
authors: [
"Quentin Hocquet <mefyl@gruntech.org>"
"Jane Street Group, LLC <opensource@janestreet.com>"
"Jeremie Dimino <jeremie@dimino.org>"
]
license: "MIT"
homepage: "https://github.com/ocaml-dune/csexp"
doc: "https://ocaml-dune.github.io/csexp/"
bug-reports: "https://github.com/ocaml-dune/csexp/issues"
depends: [
"dune" {>= "3.4"}
"ocaml" {>= "4.03.0"}
"odoc" {with-doc}
]
dev-repo: "git+https://github.com/ocaml-dune/csexp.git"
build: [
["dune" "subst"] {pinned}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
# "@runtest" {with-test & ocaml:version >= "4.04"}
"@doc" {with-doc}
]
]

View file

@ -0,0 +1,14 @@
build: [
["dune" "subst"] {pinned}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
# "@runtest" {with-test & ocaml:version >= "4.04"}
"@doc" {with-doc}
]
]

View file

@ -0,0 +1,40 @@
(lang dune 3.4)
(name csexp)
(version 1.5.2)
(license MIT)
(maintainers "Jeremie Dimino <jeremie@dimino.org>")
(authors
"Quentin Hocquet <mefyl@gruntech.org>"
"Jane Street Group, LLC <opensource@janestreet.com>"
"Jeremie Dimino <jeremie@dimino.org>")
(source (github ocaml-dune/csexp))
(documentation "https://ocaml-dune.github.io/csexp/")
(generate_opam_files true)
(package
(name csexp)
(depends
(ocaml (>= 4.03.0))
; (ppx_expect :with-test)
; Disabled because of a dependency cycle
; (see https://github.com/ocaml-opam/opam-depext/issues/121)
)
(synopsis "Parsing and printing of S-expressions in Canonical form")
(description "
This library provides minimal support for Canonical S-expressions
[1]. Canonical S-expressions are a binary encoding of S-expressions
that is super simple and well suited for communication between
programs.
This library only provides a few helpers for simple applications. If
you need more advanced support, such as parsing from more fancy input
sources, you should consider copying the code of this library given
how simple parsing S-expressions in canonical form is.
To avoid a dependency on a particular S-expression library, the only
module of this library is parameterised by the type of S-expressions.
[1] https://en.wikipedia.org/wiki/Canonical_S-expressions
"))

View file

@ -0,0 +1,6 @@
(lang dune 1.0)
;; This file is used by `make all-supported-ocaml-versions`
(context (opam (switch 4.03.0)))
(context (opam (switch 4.04.2)))
(context (opam (switch 4.08.1)))

58
unikernel/duniverse/csexp/flake.lock generated Normal file
View file

@ -0,0 +1,58 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1678901627,
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1678109515,
"narHash": "sha256-C2X+qC80K2C1TOYZT8nabgo05Dw2HST/pSn6s+n6BO8=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "aa9ff6ce4a7f19af6415fb3721eaa513ea6c763c",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1679628852,
"narHash": "sha256-mrBaaWvxYItnawGndPjQGMKpQK6nF8ljzuvm0DLgnr4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f3a0f82e577771b3cf116580b78c66e7b2a89d5c",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,42 @@
{
description = "csexp Nix Flake";
inputs.nix-filter.url = "github:numtide/nix-filter";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixpkgs.url = "github:nixos/nixpkgs";
outputs = { self, nixpkgs, flake-utils, nix-filter }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages."${system}";
inherit (pkgs.ocamlPackages) buildDunePackage;
in
rec {
packages = rec {
default = csexp;
csexp = buildDunePackage {
pname = "csexp";
version = "n/a";
src = ./.;
duneVersion = "3";
propagatedBuildInputs = with pkgs.ocamlPackages; [ ];
checkInputs = with pkgs.ocamlPackages; [
ppx_inline_test
ppx_expect
];
doCheck = true;
};
};
devShells.default = pkgs.mkShell {
inputsFrom = pkgs.lib.attrValues packages;
buildInputs = with pkgs.ocamlPackages; [
dune-release
pkgs.ccls
ocaml-lsp
pkgs.ocamlformat
ppx_bench
core_bench
];
};
});
}

View file

@ -0,0 +1,418 @@
module type Sexp = sig
type t =
| Atom of string
| List of t list
end
module type Monad = sig
type 'a t
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
end
module type S = sig
type sexp
val parse_string : string -> (sexp, int * string) result
val parse_string_many : string -> (sexp list, int * string) result
val input : in_channel -> (sexp, string) result
val input_opt : in_channel -> (sexp option, string) result
val input_many : in_channel -> (sexp list, string) result
val serialised_length : sexp -> int
val to_string : sexp -> string
val to_buffer : Buffer.t -> sexp -> unit
val to_channel : out_channel -> sexp -> unit
module Parser : sig
exception Parse_error of string
val premature_end_of_input : string
module Lexer : sig
type t
val create : unit -> t
type _ token =
| Await : [> `other ] token
| Lparen : [> `other ] token
| Rparen : [> `other ] token
| Atom : int -> [> `atom ] token
val feed : t -> char -> [ `other | `atom ] token
val feed_eoi : t -> unit
end
module Stack : sig
type t =
| Empty
| Open of t
| Sexp of sexp * t
val to_list : t -> sexp list
val open_paren : t -> t
val close_paren : t -> t
val add_atom : string -> t -> t
val add_token : [ `other ] Lexer.token -> t -> t
end
end
module type Input = sig
type t
module Monad : sig
type 'a t
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
end
val read_string : t -> int -> (string, string) result Monad.t
val read_char : t -> (char, string) result Monad.t
end
[@@deprecated "Use Parser module instead"]
[@@@warning "-3"]
module Make_parser (Input : Input) : sig
val parse : Input.t -> (sexp, string) result Input.Monad.t
val parse_many : Input.t -> (sexp list, string) result Input.Monad.t
end
[@@deprecated "Use Parser module instead"]
end
module Make (Sexp : Sexp) = struct
open Sexp
module Parser = struct
exception Parse_error of string
let parse_error msg = raise (Parse_error msg)
let parse_errorf f = Format.ksprintf parse_error f
let premature_end_of_input = "premature end of input"
module Lexer = struct
type state =
| Init
| Parsing_length
type t =
{ mutable state : state
; mutable n : int
}
let create () = { state = Init; n = 0 }
let int_of_digit c = Char.code c - Char.code '0'
type _ token =
| Await : [> `other ] token
| Lparen : [> `other ] token
| Rparen : [> `other ] token
| Atom : int -> [> `atom ] token
let feed t c =
match (t.state, c) with
| Init, '(' -> Lparen
| Init, ')' -> Rparen
| Init, '0' .. '9' ->
t.state <- Parsing_length;
t.n <- int_of_digit c;
Await
| Init, _ ->
parse_errorf "invalid character %C, expected '(', ')' or '0'..'9'" c
| Parsing_length, '0' .. '9' ->
let len = (t.n * 10) + int_of_digit c in
if len > Sys.max_string_length then
parse_error "atom too big to represent"
else (
t.n <- len;
Await)
| Parsing_length, ':' ->
t.state <- Init;
Atom t.n
| Parsing_length, _ ->
parse_errorf
"invalid character %C while parsing atom length, expected '0'..'9' \
or ':'"
c
let feed_eoi t =
match t.state with
| Init -> ()
| Parsing_length -> parse_error premature_end_of_input
end
module L = Lexer
module Stack = struct
type t =
| Empty
| Open of t
| Sexp of Sexp.t * t
let open_paren stack = Open stack
let close_paren =
let rec loop acc = function
| Empty ->
parse_error "right parenthesis without matching left parenthesis"
| Sexp (sexp, t) -> loop (sexp :: acc) t
| Open t -> Sexp (List acc, t)
in
fun t -> loop [] t
let to_list =
let rec loop acc = function
| Empty -> acc
| Sexp (sexp, t) -> loop (sexp :: acc) t
| Open _ -> parse_error premature_end_of_input
in
fun t -> loop [] t
let add_atom s stack = Sexp (Atom s, stack)
let add_token (x : [ `other ] Lexer.token) stack =
match x with
| L.Await -> stack
| L.Lparen -> open_paren stack
| L.Rparen -> close_paren stack
end
end
open Parser
let feed_eoi_single lexer stack =
match
Lexer.feed_eoi lexer;
Stack.to_list stack
with
| exception Parse_error msg -> Error msg
| [ x ] -> Ok x
| [] -> Error premature_end_of_input
| _ :: _ :: _ -> assert false
let feed_eoi_many lexer stack =
match
Lexer.feed_eoi lexer;
Stack.to_list stack
with
| exception Parse_error msg -> Error msg
| l -> Ok l
let one_token s pos len lexer stack k =
match Lexer.feed lexer (String.unsafe_get s pos) with
| exception Parse_error msg -> Error (pos, msg)
| L.Atom atom_len -> (
match String.sub s (pos + 1) atom_len with
| exception _ -> Error (len, premature_end_of_input)
| atom ->
let pos = pos + 1 + atom_len in
k s pos len lexer (Stack.add_atom atom stack))
| (L.Await | L.Lparen | L.Rparen) as x -> (
match Stack.add_token x stack with
| exception Parse_error msg -> Error (pos, msg)
| stack -> k s (pos + 1) len lexer stack)
[@@inlined always]
let parse_string =
let rec loop s pos len lexer stack =
if pos = len then
match feed_eoi_single lexer stack with
| Error msg -> Error (pos, msg)
| Ok _ as ok -> ok
else one_token s pos len lexer stack cont
and cont s pos len lexer stack =
match stack with
| Stack.Sexp (sexp, Empty) ->
if pos = len then Ok sexp
else Error (pos, "data after canonical S-expression")
| stack -> loop s pos len lexer stack
in
fun s -> loop s 0 (String.length s) (Lexer.create ()) Empty
let parse_string_many =
let rec loop s pos len lexer stack =
if pos = len then
match feed_eoi_many lexer stack with
| Error msg -> Error (pos, msg)
| Ok _ as ok -> ok
else one_token s pos len lexer stack loop
in
fun s -> loop s 0 (String.length s) (Lexer.create ()) Empty
let one_token ic c lexer stack =
match Lexer.feed lexer c with
| L.Atom n -> (
match really_input_string ic n with
| exception End_of_file -> raise (Parse_error premature_end_of_input)
| s -> Stack.add_atom s stack)
| (L.Await | L.Lparen | L.Rparen) as x -> Stack.add_token x stack
let input_opt =
let rec loop ic lexer stack =
let c = input_char ic in
match one_token ic c lexer stack with
| Sexp (sexp, Empty) -> Ok (Some sexp)
| stack -> loop ic lexer stack
in
fun ic ->
let lexer = Lexer.create () in
match input_char ic with
| exception End_of_file -> Ok None
| c -> (
try
match Lexer.feed lexer c with
| L.Atom _ -> assert false
| (L.Await | L.Lparen | L.Rparen) as x ->
loop ic lexer (Stack.add_token x Empty)
with
| Parse_error msg -> Error msg
| End_of_file -> Error premature_end_of_input)
let input ic =
match input_opt ic with
| Ok None -> Error premature_end_of_input
| Ok (Some x) -> Ok x
| Error msg -> Error msg
let input_many =
let rec loop ic lexer stack =
match input_char ic with
| exception End_of_file ->
Lexer.feed_eoi lexer;
Ok (Stack.to_list stack)
| c -> loop ic lexer (one_token ic c lexer stack)
in
fun ic ->
try loop ic (Lexer.create ()) Empty with Parse_error msg -> Error msg
let serialised_length =
let rec loop acc t =
match t with
| Atom s ->
let len = String.length s in
let x = ref len in
let len_len = ref 1 in
while !x > 9 do
x := !x / 10;
incr len_len
done;
acc + !len_len + 1 + len
| List l -> 2 + List.fold_left loop acc l
in
fun t -> loop 0 t
let to_buffer buf sexp =
let rec loop = function
| Atom str ->
Buffer.add_string buf (string_of_int (String.length str));
Buffer.add_string buf ":";
Buffer.add_string buf str
| List e ->
Buffer.add_char buf '(';
List.iter loop e;
Buffer.add_char buf ')'
in
loop sexp
let to_string sexp =
let buf = Buffer.create (serialised_length sexp) in
to_buffer buf sexp;
Buffer.contents buf
let to_channel oc sexp =
let rec loop = function
| Atom str ->
output_string oc (string_of_int (String.length str));
output_char oc ':';
output_string oc str
| List l ->
output_char oc '(';
List.iter loop l;
output_char oc ')'
in
loop sexp
module type Input = sig
type t
module Monad : Monad
val read_string : t -> int -> (string, string) result Monad.t
val read_char : t -> (char, string) result Monad.t
end
module Make_parser (Input : Input) = struct
open Input.Monad
let ( >>= ) = bind
let ( >>=* ) m f =
m >>= function
| Error _ as err -> return err
| Ok x -> f x
let one_token input c lexer stack =
match Lexer.feed lexer c with
| exception Parse_error msg -> return (Error msg)
| L.Atom n ->
Input.read_string input n >>=* fun s ->
return (Ok (Stack.add_atom s stack))
| (L.Await | L.Lparen | L.Rparen) as x ->
return
(match Stack.add_token x stack with
| exception Parse_error msg -> Error msg
| stack -> Ok stack)
let parse =
let rec loop input lexer stack =
Input.read_char input >>= function
| Error _ -> return (feed_eoi_single lexer stack)
| Ok c -> (
one_token input c lexer stack >>=* function
| Sexp (sexp, Empty) -> return (Ok sexp)
| stack -> loop input lexer stack)
in
fun input -> loop input (Lexer.create ()) Empty
let parse_many =
let rec loop input lexer stack =
Input.read_char input >>= function
| Error _ -> return (feed_eoi_many lexer stack)
| Ok c ->
one_token input c lexer stack >>=* fun stack -> loop input lexer stack
in
fun input -> loop input (Lexer.create ()) Empty
end
end
module T = struct
type t =
| Atom of string
| List of t list
end
include T
include Make (T)

View file

@ -0,0 +1,378 @@
(** Canonical S-expressions *)
(** This module provides minimal support for reading and writing S-expressions
in canonical form.
https://en.wikipedia.org/wiki/Canonical_S-expressions
Note that because the canonical representation of S-expressions is so
simple, this module doesn't go out of his way to provide a fully generic
parser and printer and instead just provides a few simple functions. If you
are using fancy input sources, simply copy the parser and adapt it. The
format is so simple that it's pretty difficult to get it wrong by accident.
To avoid a dependency on a particular S-expression library, the only module
of this library is parameterised by the type of S-expressions.
{[
let rec print = function
| Atom str -> Printf.printf "%d:%s" (String.length s)
| List l -> List.iter print l
]} *)
module type Sexp = sig
type t =
| Atom of string
| List of t list
end
module type S = sig
(** {2 Parsing} *)
type sexp
(** [parse_string s] parses a single S-expression encoded in canonical form in
[s]. It is an error for [s] to contain a S-expression followed by more
data. In case of error, the offset of the error as well as an error
message is returned. *)
val parse_string : string -> (sexp, int * string) result
(** [parse_string s] parses a sequence of S-expressions encoded in canonical
form in [s] *)
val parse_string_many : string -> (sexp list, int * string) result
(** Read exactly one canonical S-expressions from the given channel. Note that
this function never raises [End_of_file]. Instead, it returns [Error]. *)
val input : in_channel -> (sexp, string) result
(** Same as [input] but returns [Ok None] if the end of file has already been
reached. If some more characters are available but the end of file is
reached before reading a complete S-expression, this function returns
[Error]. *)
val input_opt : in_channel -> (sexp option, string) result
(** Read many S-expressions until the end of input is reached. *)
val input_many : in_channel -> (sexp list, string) result
(** {2 Serialising} *)
(** The length of the serialised representation of a S-expression *)
val serialised_length : sexp -> int
(** [to_string sexp] converts S-expression [sexp] to a string in canonical
form. *)
val to_string : sexp -> string
(** [to_buffer buf sexp] outputs the S-expression [sexp] converted to its
canonical form to buffer [buf]. *)
val to_buffer : Buffer.t -> sexp -> unit
(** [output oc sexp] outputs the S-expression [sexp] converted to its
canonical form to channel [oc]. *)
val to_channel : out_channel -> sexp -> unit
(** {3 Low level parser}
For efficiently parsing from sources other than strings or input channel.
For instance in Lwt or Async programs. *)
module Parser : sig
(** The [Parser] module offers an API that is a balance between sharing the
common logic of parsing canonical S-expressions while allowing to write
parsers that are as efficient as possible, both in terms of speed and
allocations. A carefully written parser using this API will be:
- fast
- perform minimal allocations
- perform zero [caml_modify] (a slow function of the OCaml runtime that
is emitted when mutating a constructed value)
{2 Lexers}
To parse using this API, you must first create a lexer via
{!Lexer.create}. The lexer is responsible for scanning the input and
forming tokens. The user must feed characters read from the input one by
one to the lexer until it yields a token. For instance:
{[
# let lexer = Lexer.create ();;
val lexer : Lexer.t = <abstract>
# Lexer.feed lexer '(';;
- : [ `atom | `other ] Lexer.token = Lparen
# Lexer.feed lexer ')';;
- : [ `atom | `other ] Lexer.token = Rparen
]}
When the lexer doesn't have enough to return a token, it simply returns
the special token {!Lexer.Await}:
{[
# Lexer.feed lexer '1';;
- : [ `atom | `other ] Lexer.token = Await
]}
Note that since atoms of canonical S-expressions do not need quoting,
they are always represented as a contiguous sequence of characters that
don't need further processing. To achieve maximum efficiency, the lexer
only returns the length of the atom and it is the responsibility of the
caller to extract the atom from the input source:
{[
# Lexer.feed lexer '2';;
- : [ `atom | `other ] Lexer.token = Await
# Lexer.feed lexer ':';;
- : [ `atom | `other ] Lexer.token = Atom 2
]}
When getting [Atom n], the caller should then proceed to read the next
[n] characters of the input as a string. For instance, if the input is
an [in_channel] the caller should proceed with
[really_input_string ic n].
Finally, when the end of input is reached the user should call
{!Lexer.feed_eoi} to make sure the lexer is not awaiting more input. If
that is the case, {!Lexer.feed_eoi} will raise:
{[
# Lexer.feed lexer '1';;
- : [ `atom | `other ] Lexer.token = Await
# Lexer.feed_eoi lexer;;
Exception: Parse_error "premature end of input".
]}
{2 Parsing stacks}
The lexer doesn't keep track of the structure of the S-expressions. In
order to construct a whole structured S-expressions, the caller must
maintain a parsing stack via the {!Stack} module. A {!Stack.t} value
simply represent a parsed prefix in reverse order.
For instance, the prefix "1:x((1:y1:z)" will be represented as:
{[
Sexp (List [ Atom "y"; Atom "z" ], Open (Sexp (Atom "x", Empty)))
]}
The {!Stack} module offers various primitives to open or close
parentheses or insert an atom. And for convenience it provides a
function {!Stack.add_token} that takes the output of {!Lexer.feed}
directly:
{[
# Stack.add_token Rparen Empty;;
- : Stack.t = Open Empty
# Stack.add_token Lparen (Open Empty);;
- : Stack.t = Sexp (List [], Empty)
]}
Note that {!Stack.add_token} doesn't accept [Atom _]. This is enforced
at the type level by a GADT. The reason for this is that in order to
insert an atom, the user must have fetched the contents of the atom
themselves. In order to insert an atom into a stack, you can use the
function {!Stack.add_atom}:
{[
# Stack.add_atom "foo" (Open Empty);;
- : Stack.t = Sexp (Atom "foo", Open Empty)
]}
When parsing is finished, one may call the function {!Stack.to_list} in
order to extract all the toplevel S-expressions from the stack:
{[
# Stack.to_list (Sexp (Atom "x", Sexp (List [Atom "y"], Empty)));;
- : sexp list = [List [Atom "y"; Atom "x"]]
]}
If instead you want to stop parsing as soon a single full S-expression
has been discovered, you can match on the structure of the stack. If the
stack is of the form [Sexp (_, Empty)], then you know that exactly one
S-expression has been parsed and you can stop there.
{2 Parsing errors}
In order to reduce allocations to a minumim, parsing errors are reported
via the exception {!Parse_error}. It is the responsibility of the caller
to catch this exception and return it as an [Error _] value. Functions
that may raise [Parse_error] are documented as such.
When extracting an atom and the input doesn't have enough characters
left, the user may raise [Parse_error premature_end_of_input]. This will
produce an error message similar to what the various high-level
functions of this library produce.
{2 Building a parsing function}
Parsing functions should always follow the following pattern:
+ create a lexer and start with an empty parsing stack
+ iterate over the input, feeding the lexer characters one by one. When
the lexer returns [Atom n], fetch the next [n] characters from the
input to form an atom
+ update the stack via [Stack.add_atom] or [Stack.add_token]
+ if parsing the whole input, call [Lexer.feed_eoi] when the end of
input is reached, otherwise stop as soon as the stack is of the form
[Sexp (_, Empty)] -
For instance, to parse a string as a list of S-expressions:
{[
module Sexp = struct
type t =
| Atom of string
| List of t list
end
module Csexp = Csexp.Make (Sexp)
let extract_atom s pos len =
match String.sub s pos len with
| exception _ ->
(* Turn out-of-bounds errors into [Parse_error] *)
raise (Parse_error premature_end_of_input)
| s -> s
let parse_string =
let open Csexp.Parser in
let rec loop s pos len lexer stack =
if pos = len then (
Lexer.feed_eoi lexer;
Stack.to_list stack)
else
match Lexer.feed lexer (String.unsafe_get s pos) with
| Atom atom_len ->
let atom = extract_atom s (pos + 1) atom_len in
loop s (pos + 1 + atom) len lexer (Stack.add_atom atom stack)
| (Await | Lparen | Rparen) as x ->
loop s (pos + 1) len lexer (Stack.add_token x stack)
in
fun s ->
match loop s 0 (String.length s) (Lexer.create ()) Empty with
| v -> Ok v
| exception Parse_error msg -> Error msg
]} *)
exception Parse_error of string
(** Error message signaling the end of input was reached prematurely. You
can use this when extracting an atom from the input and the input
doesn't have enough characters. *)
val premature_end_of_input : string
module Lexer : sig
(** Lexical analyser *)
type t
val create : unit -> t
type _ token =
| Await : [> `other ] token
| Lparen : [> `other ] token
| Rparen : [> `other ] token
| Atom : int -> [> `atom ] token
(** Feed a character to the parser.
@raise Parse_error *)
val feed : t -> char -> [ `other | `atom ] token
(** Feed the end of input to the parser.
You should call this function when the end of input has been reached
in order to ensure that the lexer is not awaiting more input, which
would be an error.
@raise Parse_error if the lexer is awaiting more input *)
val feed_eoi : t -> unit
end
module Stack : sig
(** Parsing stack *)
type t =
| Empty
| Open of t
| Sexp of sexp * t
(** Extract the list of full S-expressions contained in a stack.
For instance:
{[
# to_list (Sexp (Atom "y", Sexp (Atom "x", Empty)));;
- : Stack.t list = [Atom "x"; Atom "y"]
]}
@raise Parse_error
if the stack contains open parentheses that has not been closed. *)
val to_list : t -> sexp list
(** Add a left parenthesis. *)
val open_paren : t -> t
(** Add a right parenthesis. Raise [Parse_error] if the stack contains no
opened parentheses.
For instance:
{[
# close_paren (Sexp (Atom "y", Sexp (Atom "x", Open Empty)));;
- : Stack.t = Sexp (List [Atom "x"; Atom "y"], Empty)
]}
@raise Parse_error if the stack contains no open open parenthesis. *)
val close_paren : t -> t
(** Insert an atom in the parsing stack:
{[
# add_atom "foo" Empty;;
- : Stack.t = Sexp (Atom "foo", Empty)
]} *)
val add_atom : string -> t -> t
(** Add a token as returned by the lexer.
@raise Parse_error *)
val add_token : [ `other ] Lexer.token -> t -> t
end
end
(** {3 Deprecated low-level parser} *)
(** The above are deprecated as the {!Input} signature does not allow to
distinguish between IO errors and end of input conditions. Additionally,
the use of monads tend to produce parsers that allocates a lot.
It is recommended to use the {!Parser} module instead. *)
module type Input = sig
type t
module Monad : sig
type 'a t
val return : 'a -> 'a t
val bind : 'a t -> ('a -> 'b t) -> 'b t
end
val read_string : t -> int -> (string, string) result Monad.t
val read_char : t -> (char, string) result Monad.t
end
[@@deprecated "Use Parser module instead"]
[@@@warning "-3"]
module Make_parser (Input : Input) : sig
val parse : Input.t -> (sexp, string) result Input.Monad.t
val parse_many : Input.t -> (sexp list, string) result Input.Monad.t
end
[@@deprecated "Use Parser module instead"]
end
module Make (Sexp : Sexp) : S with type sexp := Sexp.t
include Sexp
include S with type sexp := t

View file

@ -0,0 +1,2 @@
(library
(public_name csexp))

View file

@ -0,0 +1,6 @@
(library
(name csexp_tests)
(libraries csexp)
(inline_tests)
(preprocess
(pps ppx_expect)))

View file

@ -0,0 +1,168 @@
module Sexp = struct
type t =
| Atom of string
| List of t list
end
module Csexp = Csexp.Make (Sexp)
open Csexp
let roundtrip x =
let str = to_string x in
match parse_string str with
| Error (_, msg) -> failwith msg
| Ok exp ->
assert (exp = x);
print_string str
let%expect_test _ =
roundtrip (Sexp.Atom "foo");
[%expect {|3:foo|}]
let%expect_test _ =
roundtrip (Sexp.List []);
[%expect {|()|}]
let%expect_test _ =
roundtrip (Sexp.List [ Sexp.Atom "Hello"; Sexp.Atom "World!" ]);
[%expect {|(5:Hello6:World!)|}]
let%expect_test _ =
roundtrip
(Sexp.List
[ Sexp.List
[ Sexp.Atom "metadata"
; Sexp.List [ Sexp.Atom "foo"; Sexp.Atom "bar" ]
]
; Sexp.List
[ Sexp.Atom "produced-files"
; Sexp.List
[ Sexp.List
[ Sexp.Atom "/tmp/coin"
; Sexp.Atom
"/tmp/dune-memory/v2/files/b2/b295e63b0b8e8fae971d9c493be0d261.1"
]
]
]
]);
[%expect
{|((8:metadata(3:foo3:bar))(14:produced-files((9:/tmp/coin63:/tmp/dune-memory/v2/files/b2/b295e63b0b8e8fae971d9c493be0d261.1))))|}]
let print_parsed r =
match r with
| Error msg -> Printf.printf "Error %S" msg
| Ok sexp -> Printf.printf "Ok %S" (Csexp.to_string sexp)
let parse s =
match parse_string s with
| Ok x -> print_parsed (Ok x)
| Error (_, msg) -> print_parsed (Error msg)
let%expect_test _ =
parse "(3:foo)";
[%expect {|
Ok "(3:foo)" |}]
let%expect_test _ =
parse "";
[%expect {| Error "premature end of input" |}]
let%expect_test _ =
parse "(";
[%expect {| Error "premature end of input" |}]
let%expect_test _ =
parse "(a)";
[%expect {| Error "invalid character 'a', expected '(', ')' or '0'..'9'" |}]
let%expect_test _ =
parse "(:)";
[%expect {| Error "invalid character ':', expected '(', ')' or '0'..'9'" |}]
let%expect_test _ =
parse "(4:foo)";
[%expect {| Error "premature end of input" |}]
let%expect_test _ =
parse "(5:foo)";
[%expect {| Error "premature end of input" |}]
let%expect_test _ =
parse "(3:foo)";
[%expect {| Ok "(3:foo)" |}]
let sexp_then_stuff s =
let fn, oc = Filename.open_temp_file "csexp-test" "" ~mode:[ Open_binary ] in
let delete = lazy (Sys.remove fn) in
at_exit (fun () -> Lazy.force delete);
output_string oc s;
close_out oc;
let ic = open_in_bin fn in
Csexp.input ic |> print_parsed;
print_newline ();
print_char (input_char ic);
close_in ic;
Lazy.force delete
let%expect_test _ =
sexp_then_stuff "(3:foo)(3:foo)";
[%expect {|
Ok "(3:foo)"
( |}]
let%expect_test _ =
sexp_then_stuff "(3:foo)Additional_stuff";
[%expect {|
Ok "(3:foo)"
A |}]
let%expect_test _ =
parse "(3:foo)(3:foo)";
[%expect {| Error "data after canonical S-expression" |}]
let%expect_test _ =
parse "(3:foo)additional_stuff";
[%expect {| Error "data after canonical S-expression" |}]
let parse_many s =
match parse_string_many s with
| Error (_, msg) -> print_parsed (Error msg)
| Ok xs -> xs |> List.iter (fun x -> print_parsed (Ok x))
let%expect_test "parse_string_many - parse empty string" =
parse_many "";
[%expect {| |}]
let%expect_test "parse_string_many - parse a single csexp" =
parse_many "(3:foo)";
[%expect {| Ok "(3:foo)" |}]
let%expect_test "parse_string_many - parse many csexp" =
parse_many "(3:foo)(3:bar)";
[%expect {| Ok "(3:foo)"Ok "(3:bar)" |}]
let%expect_test "serialised_length" =
let csexp = Sexp.Atom "xxx" in
print_endline (Csexp.to_string csexp);
print_int (Csexp.serialised_length csexp);
[%expect {|
3:xxx
5 |}];
let csexp = Sexp.List [] in
print_endline (Csexp.to_string csexp);
print_int (Csexp.serialised_length csexp);
[%expect {|
()
2 |}];
let csexp = Sexp.List [ Atom "xxx" ] in
print_endline (Csexp.to_string csexp);
print_int (Csexp.serialised_length csexp);
[%expect {|
(3:xxx)
7 |}];
let csexp = Sexp.List [ Atom "xxx"; Atom "xxx" ] in
print_endline (Csexp.to_string csexp);
print_int (Csexp.serialised_length csexp);
[%expect {|
(3:xxx3:xxx)
12 |}]