Compare commits

..

51 commits
main ... 1.5

Author SHA1 Message Date
Simon Cruanes
a91d504c23 ready for 1.5 2018-01-02 18:15:52 +01:00
Simon Cruanes
46e41f7f84 Merge branch 'master' into stable for 1.5 2018-01-02 18:14:49 +01:00
Simon Cruanes
ad8a61a795 Merge branch 'master' into stable 2017-10-11 09:30:53 +02:00
Simon Cruanes
f24d983b04 Merge branch 'master' into stable for 1.3 2017-07-29 18:13:25 +02:00
Simon Cruanes
32bc0450dc Merge branch 'master' into stable for 1.2 2017-05-01 17:02:29 +02:00
Simon Cruanes
461d6309e7 version 1.1 2017-03-03 16:19:40 +01:00
Simon Cruanes
9fb319966b Merge branch 'master' into stable for 1.1 2017-03-03 16:18:26 +01:00
Simon Cruanes
e52b847e90 Merge branch 'master' into stable for 1.0 2017-02-11 14:31:14 +01:00
Simon Cruanes
265ed02dc6 Merge branch 'master' into stable for 0.22.1 2016-12-29 10:47:40 +01:00
Simon Cruanes
9c80dba89e Merge branch 'master' into stable 2016-12-18 01:35:58 +01:00
Simon Cruanes
d84b1e1369 Merge branch 'master' into stable for 0.21 2016-11-03 16:07:17 +01:00
Simon Cruanes
88025034b6 release 0.20 2016-10-14 11:35:39 +02:00
Simon Cruanes
f6774434cd Merge branch 'master' into stable 2016-10-14 11:34:02 +02:00
Simon Cruanes
47978e4bba Merge branch 'master' into stable for 0.19 2016-08-22 10:01:36 +02:00
Simon Cruanes
ed2b741865 Merge branch 'master' into stable for 0.18 2016-06-14 14:13:29 +02:00
Simon Cruanes
e69ad8a6de Merge branch 'master' into stable 2016-04-22 23:42:29 +02:00
Simon Cruanes
94396b79e0 small release, 0.16.1, for the oasis fix 2016-03-09 10:20:03 +01:00
Simon Cruanes
d541de5d03 re-generate oasis files; remove dep on oasis in opam 2016-03-09 10:13:30 +01:00
Simon Cruanes
c16af69fb3 update opam file 2016-02-24 22:16:33 +01:00
Simon Cruanes
ab183a7348 Merge branch 'master' into stable for 0.16 2016-02-24 22:09:31 +01:00
Simon Cruanes
42c912fe0e Merge branch 'master' into stable for 0.15 2015-12-22 10:38:21 +01:00
Simon Cruanes
8eac492c21 0.14.0.1 with some bugfixes 2015-11-11 17:43:07 +01:00
Simon Cruanes
adc37e48b3 version 0.14 2015-11-08 13:04:55 +01:00
Simon Cruanes
844d39c826 Merge branch 'master' into stable; version 0.13 2015-09-23 16:40:38 +02:00
Simon Cruanes
4e49e2a893 Merge branch 'master' into stable 2015-07-16 11:28:19 +02:00
Simon Cruanes
7bacac2c98 Merge branch 'master' into stable; oasis setup; 0.11 2015-05-24 21:52:22 +02:00
Simon Cruanes
9f562cd657 opam file 2015-04-10 16:49:46 +02:00
Simon Cruanes
e268f2d10c Merge branch 'master' into stable for 0.10 2015-04-10 16:43:16 +02:00
Simon Cruanes
99df5baac2 merge from master 2015-03-17 00:40:09 +01:00
Simon Cruanes
4ed1691eb9 merge bugfixes from master 2015-03-02 16:52:51 +01:00
Simon Cruanes
8054a9f256 version 0.9 (merge from master) 2015-02-27 14:41:09 +01:00
Simon Cruanes
d4fa455365 merge from master (bis) 2015-01-26 20:35:17 +01:00
Simon Cruanes
0d61b48fdd merge from master 2015-01-26 20:28:53 +01:00
Simon Cruanes
a21f097a64 version 0.8 2015-01-26 20:24:52 +01:00
Simon Cruanes
d534d4c50e only build doc if all the required flags are enabled 2014-12-22 17:59:14 +01:00
Simon Cruanes
2efbc8d56b add dependency on sequence for containers.advanced 2014-12-19 20:58:02 +01:00
Simon Cruanes
a8f5c3420f merge from master for 0.7 2014-12-19 20:23:13 +01:00
Simon Cruanes
41e6e5ec75 bugfix in CCIO.read_all and CCIO.read_chunks 2014-12-16 22:36:54 +01:00
Simon Cruanes
31fdc16185 forgot an odocl file 2014-12-08 12:06:40 +01:00
Simon Cruanes
b91d42912a Merge branch 'master' into stable 2014-12-08 10:39:28 +01:00
Simon Cruanes
1b15573acd merge from master; version 0.6 2014-11-23 14:37:21 +01:00
Simon Cruanes
22d9d27c80 version 0.5 2014-11-12 11:03:13 +01:00
Simon Cruanes
e0a47cba9b Merge branch 'master' into stable 2014-11-12 00:03:09 +01:00
Simon Cruanes
608edd9a1a version 0.4.1 2014-10-19 21:59:37 +02:00
Simon Cruanes
0e555bed6c merge from master 2014-10-19 21:14:17 +02:00
Simon Cruanes
2cb86cb93f version 0.4 2014-09-30 17:21:58 +02:00
Simon Cruanes
4e3631c12b forgot build files 2014-09-30 17:17:10 +02:00
Simon Cruanes
a3cfdacc78 oasis files 2014-09-30 17:12:59 +02:00
Simon Cruanes
7d3742e765 merge from master 2014-09-30 17:12:59 +02:00
Simon Cruanes
2fb05ad8b5 merge from master 2014-09-28 15:25:52 +02:00
Simon Cruanes
efc3dcb44d merge from stable (including sequence subtree); 0.3.4 2014-08-09 00:21:50 +02:00
406 changed files with 31414 additions and 44981 deletions

View file

@ -1,28 +0,0 @@
name: format
on:
push:
branches:
- main
pull_request:
jobs:
format:
name: format
strategy:
matrix:
ocaml-compiler:
- '5.3'
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@main
- name: Use OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
dune-cache: true
allow-prerelease-opam: true
- run: opam install ocamlformat.0.27.0
- run: opam exec -- make format-check

View file

@ -1,33 +0,0 @@
name: github pages
on:
push:
branches:
- master # Set a branch name to trigger deployment
jobs:
deploy:
name: Deploy doc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- name: Use OCaml
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: '5.2'
dune-cache: false
- name: Deps
run: opam install odig containers containers-data
- name: Build
run: opam exec -- odig odoc --cache-dir=_doc/ containers containers-data
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./_doc/html/
destination_dir: dev
enable_jekyll: true

View file

@ -1,64 +0,0 @@
name: Build and Test
on:
push:
branches:
- main
pull_request:
jobs:
run:
name: build
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
os:
- ubuntu-latest
ocaml-compiler:
- '4.08'
- '4.10'
- '4.14'
- '5.3'
- 'ocaml-variants.5.0.0+options,ocaml-option-bytecode-only'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@main
- name: Use OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
dune-cache: true
allow-prerelease-opam: true
- run: opam install -t containers containers-data --deps-only
- run: opam exec -- dune build '@install'
- run: opam exec -- dune runtest --force --profile=release
compat:
name: build
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
os:
- macos-latest
- ubuntu-latest
#- windows-latest
ocaml-compiler:
- '5.1'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@main
- name: Use OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ matrix.ocaml-compiler }}
dune-cache: true
allow-prerelease-opam: true
- run: |
opam install -t containers --deps-only ;
opam install containers-data --deps-only # no test deps
- run: opam exec -- dune build '@install'
- run: opam exec -- dune runtest -j 1 -p containers --profile=release # test only core on non-ubuntu platform

11
.gitignore vendored
View file

@ -4,15 +4,8 @@ _build
*.native *.native
*.byte *.byte
.session .session
TAGS
*.docdir *.docdir
setup.* setup.*
qtest*
*.html *.html
.merlin
*.install
.ignore
_opam
*.exe
fuzz-*-input
fuzz-*-output
fuzz-logs/
doc/papers

21
.merlin Normal file
View file

@ -0,0 +1,21 @@
S src/core
S src/data/
S src/iter/
S src/sexp/
S src/threads/
S src/string
S benchs
S examples
S tests
B _build/src/**
B _build/benchs
B _build/examples
B _build/tests
PKG oUnit
PKG benchmark
PKG result
PKG threads
PKG threads.posix
PKG lwt
PKG qcheck
FLG -w +a-4-44-48-60@8

View file

@ -1,15 +0,0 @@
version = 0.27.0
profile=conventional
margin=80
if-then-else=k-r
parens-ite=true
parens-tuple=multi-line-only
sequence-style=terminator
type-decl=sparse
break-cases=toplevel
cases-exp-indent=2
field-space=tight-decl
leading-nested-match-parens=true
module-item-spacing=compact
quiet=true
parse-docstrings=false

23
.ocamlinit Normal file
View file

@ -0,0 +1,23 @@
#use "topfind";;
#thread
#require "result";;
#require "unix";;
#require "sequence";;
#directory "_build/src/core";;
#directory "_build/src/unix";;
#directory "_build/src/iter";;
#directory "_build/src/data";;
#directory "_build/src/sexp";;
#directory "_build/src/threads";;
#directory "_build/src/top/";;
#load "containers.cma";;
#load "containers_iter.cma";;
#load "containers_data.cma";;
#load "containers_unix.cma";;
#load "containers_sexp.cma";;
#load "containers_string.cma";;
#load "containers_top.cma";;
#thread;;
#load "containers_thread.cma";;
(* vim:syntax=ocaml:
*)

2
.ocp-indent Normal file
View file

@ -0,0 +1,2 @@
match_clause=2
with=2

37
.travis.yml Normal file
View file

@ -0,0 +1,37 @@
language: c
env:
- OCAML_VERSION=4.01.0
- OCAML_VERSION=4.02.3
- OCAML_VERSION=4.04.2
- OCAML_VERSION=4.05.0
- OCAML_VERSION=4.05.0+flambda
- OCAML_VERSION=4.06.0
addons:
apt:
sources:
- avsm
packages:
- opam
# Caching may take a lot of space with so many ocaml versions
#cache:
# directories:
# - $HOME/.opam
before_install:
# Some opam boilerplate
- export OPAMYES=1
- export OPAMVERBOSE=1
- opam init
- opam switch ${OCAML_VERSION}
- eval `opam config env`
install:
# Install dependencies
- opam pin add --no-action containers .
- opam install oasis
- opam install --deps-only containers
script:
- ./configure --enable-unix --enable-thread --disable-tests --disable-bench
- make build
- opam install sequence qcheck qtest gen
- ./configure --enable-unix --enable-thread --enable-tests --enable-docs --disable-bench
- make test
- make doc

26
AUTHORS.adoc Normal file
View file

@ -0,0 +1,26 @@
= Authors and contributors
- Simon Cruanes (`companion_cube`)
- Drup (Gabriel Radanne)
- Jacques-Pascal Deplaix
- Nicolas Braud-Santoni
- Whitequark (Peter Zotov)
- hcarty (Hezekiah M. Carty)
- struktured (Carmelo Piccione)
- Bernardo da Costa
- Vincent Bernardoff (vbmithr)
- Emmanuel Surleau (emm)
- Guillaume Bury (guigui)
- JP Rodi
- Florian Angeletti (@octachron)
- Johannes Kloos
- Geoff Gole (@gsg)
- Roma Sokolov (@little-arhat)
- Malcolm Matalka (`orbitz`)
- David Sheets (@dsheets)
- Glenn Slotte (glennsl)
- @LemonBoy
- Leonid Rozenberg (@rleonid)
- Bikal Gurung (@bikalgurung)
- Fabian Hemmer (copy)
- Maciej Woś (@lostman)

View file

@ -1,561 +1,6 @@
= Changelog
== 1.5
## 3.17
- feat: add `CCAtomic.update_cas`
- feat: add `Pvec.flat_map`
- faster `List.take_drop` thanks to a trick by nojb
- move to ocamlformat 0.27, format code
- test: enrich pvec test
- Patch CBor roundtrip property to hold for nan's too (thanks @jmid)
## 3.16
- breaking: Renamed predicate parameter of `take_while`, `rtake_while` from `p` to `f`, aligining it with pre-existing `drop_while`.
- feat: add `containers.leb128` library
- feat: add `CCFun.with_return`
- Added functions to the `Char` module to check common character properties.
- feat: add `CCVector.findi`
- fix: compat with OCaml 5.4
- fix: oob(!!) in CCHash.bytes
## 3.15
- Add `CCList.split_result` (#459)
- pretty printer in MultiSet
- `CCHeap`: building a heap from an almost-sorted sequence
- perf: `CCHeap`: building a heap from n elements is now in time O(n)
instead of O(n log n)
- perf: `CCHeap`: `filter` and `delete_all` are now in time O(n)
instead of O(n log n), and they ensure physical equality
(for `delete_all` this is a bugfix)
## 3.14
- predicate combinators: `and_pred` and `or_pred`
- feat `pp`: add a bunch of extensions
- Kleisli Composition Operator and Apply_or for option/result/fun (#455)
- add `CCByte_buffer.to_slice`
- add a byte slice type `CCByte_slice`
- add `cons_when` to `CCListLabels`
- add `(|||>)` and `||>` to `CCFun`
- `CCVector`: Add function foldi
- add `containers.pvec`, a persistent vector type.
- perf: use a monomorphic impl for `CCMonomorphic.{min,max}`
## 3.13.1
- list: TRMC was in 4.14, we can use it earlier
- fix insidious bug in CCList.flat_map linked to unspecified
evaluation order
- perf: use `concat_map` for `CCList.flat_map` on >= 5.1
(this also re-fixes the same bug in `CCList.flat_map` anyway)
## 3.13
- breaking: delete containers-thread (which was deprecated)
- breaking: pp: modify `Ext.t` so it takes surrounding value
- breaking: remove CCShims
- CCMultiMap: Rename functions find_left and find_right in the bidirectional multimap
to find_left_iter and find_right_iter respectively to reflect their usage,
and add new functions to replace the old find_left and find_right
that return a list of values rather than an iterator,
to make the signatures of CCMultiMap.S and CCMultiMap.BIDIR cohere.
Additionally, change the return type of
`S.find_iter` from `t -> key -> (value -> unit) -> unit` to `t -> key -> value iter`.
- CCList: add `unfold`
- CCBool: Add functions if_then and if_then_else
- CCList: remove some functions that are subsumed by the stdlib
- CCList: use TRMC for many functions on 5.1
- feat CCFunvec: add `fold_rev`
- add `Containers_pp.newline_or_spaces`
- cleanup: remove stubs for code always present on 4.08, rely on
newer functions in 5.1
- perf: accelerate `List.append` and `List.flat_map` on 5.1
- more warnings, more tests, cleanup dead code
- change COC to ocaml-coc
## 3.12
- add `containers.pp` sublibrary, with Wadler-style pretty printing combinators
- add `CCArray.{max,argmax,min,argmin}` and their _exn counterparts
- add `CCParse.take_until_success`
- add `Option.flat_map_l`
- add `CCSet.{find_first_map,find_last_map}`
- `CCHash`: native FNV hash for int64/int32
- fix bugs in CCParse related to `recurse` and `Slice`
- fix: fix Set.find_last_map on OCaml 4.03
- fix: make sure `Vector.to_{seq,gen}` captures the length initially
## 3.11
- official OCaml 5 support
- add `CCFun.(let@)` (if OCaml >= 4.08)
- add `CCHet.Tbl.{clear,reset}`
- fix(CCVector): concurrent modification safety in `resize_with`
- fix(CCVector): always obtain a copy of array before using unsafe_{set,get}
- CI: add ocaml 5.0.x
## 3.10
- `CCArray`: add `mapi_inplace`
- add sublibrary `containers.scc` for strongly connected components
- `CCSeq`: add `concat_map`
- `CCSeq`: add some missing function from 4.14
- add `CCInt64.{hash,hash_to_int64}`
- `Ref`: add `protect` function
- fix: include `Seq` in `CCSeq` for ocaml >= 4.07
## 3.9
- feat: add `Containers_cbor` module
- feat(CCInt32): add popcount function
- feat(CCInt64): add `popcount` operation
- CCBV:
* more extensive test suite
* use `bytes` underneath, not an array of integers
- add `containers_testlib`, removing qtest and ounit.
- `cbor`: use int64 as main int type
- fix: handle uppercase in string/hex
## 3.8
- add `Containers_bencode` for lightweight (de)ser
- perf(CCHash): improve a bit commutative hashing of arrays/lists
- perf(CCHash): only hash prefix of string/bytes
- feat(CCList): Add `Assoc.{keys,values,map_values}`
- feat(CCArray): add `CCArray.map_inplace`
- add `CCString.{to_hex,of_hex}`
- fix(Atomic): prevent race conditions under flambda, for now
## 3.7
- add `Format.styling`
- make `Format` compatible with OCaml 5.0, using Stag for colors
- new preprocessor, compatible with merlin, using `[@@@ifge 4.12]`-style pragmas
- feat: add `Byte_buf`, a byte buffer.
- add `CCSeq.{zip_i,of_string}`
- add `CCResult.opt_map` to simplify result function application over optionals (#397)
- add shims for Atomic and Unit
- expose `CCParse.pos` to get current pos; improve perf
- add `CCVector.resize_with` and `CCVector.resize_with_init`, tests and doc (#389)
- add `CCVector.insert`
- update dune to 1.10, condition some rules to unix
- perf: reduce allocations in `CCSeq.to_array`
- fix asymptotic behavior of resize functions in `CCVector`
- fix: rely on `either` compatibility library
## 3.6.1
- use `either` compatibility library instead of shims
## 3.6
- rename `CCOpt` to `CCOption` and deprecate `CCOpt`
- add iterator functions to `CCIO`
- `CCOrd`: add `poly`, deprecate `compare`
- add `CCIO.File.walk_iter`
- `CCParse`: heavy refactoring, many new functions
* backtracking by default
* add `slice` and the ability to recurse on them
* expose Position module, add `or_`, `both`, `lookahead`, `U.bool`
* example Sexpr parser, and a test
* example and test of an IRC log parser
- fix bug in `CCIO.read_lines_seq`
## 3.5.1
- fix bug in `CCIO.read_lines_seq` (backported from 3.6)
## 3.5
- add `CCHash.map` and `CCHash.bytes`
- CCIO: add many `Seq.t` based functions
- CCUtf8string: add `{make,empty,of_uchar}`
- add `CCFormat.{const_string,opaque}`
- add `CCOpt.{some,none}`
- CCFormat: expose `ANSI_codes` module
- CCBV: add `equal`, refactor for performance and readability
- CCList: add `{sorted_diff_uniq,sorted_mem,sorted_diff,sorted_remove}`
- fix(bv): index error in union
- test: add some property tests on `Csexp/Canonical_sexp`
- bv: add more tests, including regression for #370
## 3.4
- Add `CCOpt.get_exn_or` and deprecate `CCOpt.get_exn`
- CCRAL: add `get_and_remove_exn` operation
- CCString: add `CCString.uniq`
- refactor `CCHash` to use FNV in many combinators
- CCInt: improve perf by using a single implementation of popcount using int64
- fix: CCRAL.remove does not always remove
- fix(sexp): re-export the `loc` type to the functor's argument's type
- refactor and clarify `cutoff` in `String.edit_distance`
- fix(CCInt): make sure hash is always positive
- big upgrade to CI thanks to @Fardale
## 3.3
- feat: add code-generator for optimal bitfields; add tests
- new Canonical sexpr module with printer and parser
- CCSeq: Add `for_all` and `exists`
- feat(sexp): expose last location in decoder
- feat(CCChar): add CCChar.Infix
- feat(CCString): add CCString.foldi
- feat(CCFormat): add `string_lines` combinator
- feat(CCList): update with regards to `partition_map`
- add `CCList.cons'`
- implement {of,add}_*_with family of function in `CCMap` with update (#352)
- add `CCMap.of_{list,iter,seq}_with` functions
- add `CCHashtbl.{of,add}_{list,seq,iter}_with`
- Fix integer overflow warning on jsoo (#346)
- updated fuzzer scripts
### Containers-thread
- refactor(pool): less locking, fix deadlock, more parallelism
- feat(pool): keep one idle thread
- small optim in `Pool.sequence_a`
## 3.2
- add CCEither module
- add `CCList.chunks`
- add iter/seq functions to `CCString`
- add `CCList.reduce` (resolves #305)
- fix: in `CCInt` pick popcount at runtime on 64 bits
- fix: in shims, use configurator properly to determine int size
- in `CCFormat`, add `append`, `append_l`, infix `++` for sequencing,
`space`, `break`, `cut`
- fix: in `CCSexp`, handle non-ascii escapes in strings
- `CCUtf8_string`: add and expose `uchar_to_bytes`
- enable auto deploy of doc
- improve CI: test core on non ubuntu platform, test all on ubuntu
- update readme
- CCImmutArray: add tests (#344)
- add fuzzing (#339)
- add stronger test to compare with uutf in ccutf8string
## 3.1
- add `List.combine_chop` and corresponding `(and&)` synchronized product
- chore: remove travis to use github CI instead
- add `CCList.mguard` function for list comprehensions
- add some basic tests to CCMutHeap
- un-specify order of elements in `CCMap.to_list`
- Move definition of `CCMap.update` so that it is shadowed by Stdlib.Map.update
- fix(intmap): order of arguments for the HO param should be stable
- feat(containers-data): add `CCMutHeap` mutable heap with increase/decrease
## 3.0.1
- fix build on 32 bits architectures
## 3.0
### Breaking changes
see https://github.com/c-cube/ocaml-containers/issues/290 for a summary of
a subset of these changes.
packaging:
- split the library into separate packages
`containers`, `containers-data`, and `containers-thread`.
- delete `containers.iter` and merge parts of it into `containers-data`;
- move `CCSexp` into the core library, remove `containers.sexp`.
api:
- remove slice APIs in string and array.
- change pp functions to take unit printer for sep/stop/start (#295)
- CCPair: use more standard name for some map functions (#316)
- add CCSeq module, mostly adapted from `CCKlist`
- remove `CCKlist` from everywhere
- CCGraph: remove deprecated module and function
- rename `<op>_std_seq` to `<op>_seq`, making `Seq.t` the standard everywhere;
remove the old `<op>_seq` that were previously
deprecated in favor of `<op>_iter`.
- CCVector: rename `shrink` into `truncate`
- CCVector: rename `remove to CCVector.remove_unordered`
- CCList: make mem compatible with the Stdlib by making `?eq` optional
- CCVector: rename `filter'` into `filter_in_place`
### Other changes
- CI: add github actions in addition to travis
- feat: add infix operators to `String`
- feat: add opt.bind
- CCResult: add `<$>` operator
- CCResult: add `get_lazy`
- put infix operators in `Infix` module, then include it
- ccnativeint: complete CCNativeint with regards to CCInt
- Int64: complete CCInt64 with regards to CCInt
- CCInt32: complete CCInt32 with regards to CCInt
- implement `CCInt.sign` using `CCInt.compare`
- CCInt: include module Int for ocaml >= 4.08
- CCInt: add `of_float`
- CCInt: add `of_string_exn`
- add `CCResult.get_lazy`
- add `Int.popcount` operator
- CCFloat: add `pi`
- CCFloat: add `of_string_opt`
- fix: expose `always_eq`/`never_eq` in `CCEqual`
- string: add optional `cutoff` arg on `String.edit_distance`
- CCVector: add `remove_and_shift`
- CCArray: add optional argument eq to mem
- CCSexp: Escape empty atoms
- substitute 'Pervasives' with 'Stdlib'
- CCFormat: add `exn` combinator
- IO: add `copy_into` for transferring data between channels
- Extend benchmark: `to_array`, cons and `cons_fold`
- Extend benchmark: Sek, iter and pop
- benchmark for memory usage of data structures
And many, many bugfixes.
## 2.8.1
- add missing `CCVector.of_iter`
## 2.8
### Breaking:
- bump minimum version of OCaml to 4.03, drop deps `{result,uchar}`
- deprecate `{of,to}_seq` a bit everywhere
- deprecate `CCKList` as it's subsumed by `Seq`
- feat: on `>= 4.08`, support let+ and let* operators
- feat(list): add indexed functions and `fold_on_map`
- refactor: also port `CCGraph` to iter
- feat: add `{to,of,add}_{iter,std_seq}` where relevant
- feat(unix): add `ensure_session_leader` and add some docs
- feat(pool): add infix operators on futures
- fix(pp): improve printing of hashtables
- feat: add `monoid_product` to Array and Vector
- improved gc behavior for `CCvector`
- deprecate `CCVector.fill_empty_slots_with`
- `CCVector.shrink_to_fit` to limit memory usage
- add `CCVector.clear_and_reset`
- feat(sexp): expose `parse_string_list` and the list decoder
- add `CCUnix.with_temp_dir` function
- deprecate `CCOpt.to_seq`, provide `to_iter` instead
- add `CCOpt.value` to improve compat with `Stdlib.Option`
- add `CCVector.mapi`
- fix: restore `CCSexp.atom` and `list` which was lost in 2.7
- fix(sexp): set location properly when parsing a file
- fix: properly alias to `CCChar` in containers.ml
- use older dune dialect
- remove unlabel, remove all traces of Result
- require dune configurator explicitly in opam
- Re-enable mdx tests
- fix benchs so they don't depend on clarity and they compile again
## 2.7
- deprecate CCKList in favor of the standard Seq
- CCIO: add `_gen` suffixes to some functions
- ccsexp: provide ability to annotate parsed S-exprs with their position
- ccsexp: functorize the parser/printer
- ccsexp: support `#;` for commenting a sexp
- fix: remove dep from vec to list
- add `to_string` to many modules (#270)
- add `CCDeque.{remove_*;update_*}`,` CCDeque.{*_opt}`
- add `CCDeque.{filter,filter_map}`
- add `CCDeque.filter_in_place`
- add `CCBool.{to,of}_int`
- add `Result.flatten_l` to turn a list of results into a result of list
- refactor: remove stdlib's code, simple reimplementation of `Stdlib.Fun`
- add `CCArray.Infix`
- Document behaviour of `Fun.finally` when finaliser raises
- travis: test on OCaml 4.09, too.
- more docs for IO
## 2.6.1
bugfix release:
- fix(parse): error in `many`
- chore: add 4.08 to travis
- fix `Containers.Stdlib` on OCaml 4.07
## 2.6
- introduce shim modules for 4.08 compat
- remove reference to sequence, use `iter` instead for tests
- add `remove` function to het map/tbl
- missing type annotation for specializing int.compare
- doc: fix bad example in CCIO
- use `iter`, not `sequence`, in tests
- fix: use same evaluation order as stdlib for `CCList.init`
- fix: make `Array.random_choose` fail on empty array at creation time
- breaking: make `Array.random_choose` raise invalid_arg instead of not_found
- migrate readme to .md, using mdx to test it
## 2.5
- perf: annotate types in monomorphic/float/int to help specialize builtins
- use GADT to discard impossible case on `CCFQueue` (@dinosaure).
- fix(funvec): expose `pop`, fix off by one error
## 2.4.1
- revert some compatibility-breaking changes in label modules
## 2.4
### breaking:
- rename `Random.sample_without_{replacement,duplicates}`
### Features
- add `CCResult.iter_err`
- add `CCEqual.{always,never}_eq`
- add `containersLabels.ml`, generate unlabelled interfaces from labelled ones
- add `CCEqualLabels`
- add `CCArray_sliceLabels`
- add `CCStringLabels`
- add `CCResult.get_or_failwith`
- add `CCInt.( ** )` for integer exponentiation
- add `List.counts`, related to `List.count` (#230)
- migrate to dune
- migrate to opam2
- add CODE_OF_CONDUCT.md
### Fixes
- #235: release memory in vector/ringbuffer (thanks to @copy)
- remove spurious `Labels` module
- doc: fix small inaccuracy in comments and API
- test: improve perf by changing random gens
## 2.3
- feat(vector): add `Vector.{filter,filter_map}_in_place`
- perf(hashtrie): use int64 for 64-bits branching factor and popcount
- feat(intmap): add `CCIntMap.{filter,filter_map,merge,is_empty}`
- Add `CCHeap.Make_from_compare` (#225)
- add relational ops `CCList.{group_by,join,join_by,join_all_by,group_join_by}`
- fix(float): make `Float.{min,max}` compliant with revised IEEE754
- fix(build): remove `[@inline]` attributes since they break on 4.02.3
- Fix Int32 and Int64 operators that are not visible (#224)
- some performance tweaks in Vector
- test(float): add some tests for FP min/max
## 2.2
- Improving comments presentation
- Add `CCOpt.return_if`
- Add `CCOpt.flatten`
- Add `CCString.{,r}drop_while`
- add many missing functions to `CCListLabels`
- test: consistency `CCList{,Labels}`
- fix(arrayLabels): compatibility with 4.07
- fix: compatibility for CCArrayLabels
- test: add compatibility checks between `CCArray{,Labels}`
## 2.1
- make `CCInt64` compatible with `Int64` (breaking!) (closes #192)
- Add `CCBijection` in containers.data
- feat(mono): add dotted comparison operators for floats
- add `?margin` parameter to `CCFormat.ksprintf`
- add `CCUtf8_string` with basic encoding and decoding functionalities
- Add `CCLazy_list.<|>`
- Adding `CCNativeint`
- enrich `CCInt.Infix` to get a uniform interface with `CCInt{32,64}`
- add `CCInt{32,64}.Infix`
- Adding CCInt32 module
- add `CCHash.combine{5,6}`
- Add infix operators to CCFloat
- feat(list): add `{interleave,intersperse}` (closes #191)
- add missing signatures of `CCArrayLabels` (closes #193)
- Add CCFun.iterate
- add experimental `CCFun_vec` data structure for fast functional vectors
- fix: strong type aliases in Random (closes #210)
- use standard `List.sort_uniq`
- remove explicit dep on `bytes` in jbuild files
- update printers names in containers.top (closes #201)
- Enable support for Travis CI and Appveyor
- test deps are required when we run tests
- point to JST's blog post on poly compare
## 2.0
### breaking
- move to jbuilder (closes #165), requiring at least OCaml 4.02
- become defensive w.r.t polymorphic operators:
* Internally shadow polymorphic operators and functions from Pervasives
by `include CCMonomorphic` in `Containers` module
* Shadow the physical equality operator
* Shadow polymorphic functions in `CCList`
- rename `print` to `pp` for Format printers (closes #153, #181)
- remove `CCFlatHashtbl`
### others
- many typos and style fixes (from Fourchaux)
- Add `CCList.iteri2` and `CCList.foldi2`
- remove `PARAM.min_size` in `CCPool`
- Add `CCEqual.physical`
- Avoid uses of the polymorphic operators
- Add a `CCMonomorphic` module shipped into a `containers.monomorphic` library
- make complexity of `Array.lookup` explicit (closes #174)
- add `CCFormat.lazy_{or,force}` for printing thunks
- now that ocaml >= 4.02 is required, use `Format.pp_print_text` directly
- add `CCHeap.delete_{one,all}`
- add `CCList.tail_opt`
- remove qtest makefile and use a script instead
- add many tests
- fix bug in `CCRAL.drop` (see #184)
- `CCFormat`: fix support of unrecognized styles
- fix bug: don't reverse twice in `CCList.repeat`
## 1.5.1, 1.5.2
- re-export `Format` types and functions in `CCFormat`
## 1.5
- have `CCList.{get,insert,set}_at_idx` work with negative indices - have `CCList.{get,insert,set}_at_idx` work with negative indices
- Add CCCache.add - Add CCCache.add
@ -577,7 +22,7 @@ bugfix release:
- update doc of `CCList.cartesian_product`, which returns results in unspecified order (close #154) - update doc of `CCList.cartesian_product`, which returns results in unspecified order (close #154)
- fix containers.top (closes #155) - fix containers.top (closes #155)
## 1.4 == 1.4
- add `CCMap.union` - add `CCMap.union`
- add `CCRef.swap` - add `CCRef.swap`
@ -597,7 +42,7 @@ bugfix release:
- More tests for CCVector.append and CCVector.append_array - More tests for CCVector.append and CCVector.append_array
- assertions and cleanup in `CCPool` - assertions and cleanup in `CCPool`
## 1.3 == 1.3
- deprecate `CCBool.negate` - deprecate `CCBool.negate`
- add `CCString.compare_natural` (closes #146) - add `CCString.compare_natural` (closes #146)
@ -615,7 +60,7 @@ bugfix release:
- cleanup and refactor of `CCRingBuffer` (see #126). Add strong tests. - cleanup and refactor of `CCRingBuffer` (see #126). Add strong tests.
- add rich testsuite to `CCIntMap`, based on @jmid's work - add rich testsuite to `CCIntMap`, based on @jmid's work
## 1.2 == 1.2
- make many modules extensions of stdlib (close #109) - make many modules extensions of stdlib (close #109)
the modules are: `String List ListLabels Array ArrayLabels Char Random` the modules are: `String List ListLabels Array ArrayLabels Char Random`
@ -649,9 +94,9 @@ bugfix release:
- build unix support by default - build unix support by default
- bugfix and test for `CCZipper.is_focused` (closes #102) - bugfix and test for `CCZipper.is_focused` (closes #102)
- use boxes in `CCFormat.Dump` for tuples - use boxes in `CCFormat.Dump` for tuples
- update header, and use more `(##)` in `CCIntMap` - update header, and use more `(==)` in `CCIntMap`
## 1.1 == 1.1
**bugfixes**: **bugfixes**:
@ -672,7 +117,7 @@ bugfix release:
- remove CCError from tutorial - remove CCError from tutorial
- merge tutorial into readme, cleanup - merge tutorial into readme, cleanup
## 1.0 == 1.0
See https://github.com/c-cube/ocaml-containers/issues/84 for an overview. See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
@ -735,14 +180,14 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- add doc for `of_list` in relevant modules (close #85) - add doc for `of_list` in relevant modules (close #85)
- bugfix: do not use `Sequence.flatMap` (close #90) - bugfix: do not use `Sequence.flatMap` (close #90)
## 0.22 == 0.22
- threads/CCLock: add `try_with_lock` to wrap `Mutex.try_lock` - threads/CCLock: add `try_with_lock` to wrap `Mutex.try_lock`
- Add `CCMultiSet.remove_all` - Add `CCMultiSet.remove_all`
- document errors in `CCIO` (close #86) - document errors in `CCIO` (close #86)
- use the new qtest/qcheck - use the new qtest/qcheck
## 0.21 == 0.21
- (breaking) make default `start`/`stop` arguments empty in printers (#82) - (breaking) make default `start`/`stop` arguments empty in printers (#82)
@ -751,13 +196,13 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- add `CCArray.Sub.to_list` - add `CCArray.Sub.to_list`
- add `CCArray.{sorted,sort_indices,sort_ranking}` (closes #81) - add `CCArray.{sorted,sort_indices,sort_ranking}` (closes #81)
- handle `\r` in CCSexpM (fixes #83) - handle '\r` in CCSexpM (fixes #83)
- add alias `Containers.IO` - add alias `Containers.IO`
- bugfixes in `CCArray.Sub` - bugfixes in `CCArray.Sub`
- bugfix + tests for `CCArray.Sub.sub` - bugfix + tests for `CCArray.Sub.sub`
- disable parallel build to support cygwin - disable parallel build to support cygwin
## 0.20 == 0.20
- bugfix in `CCArray.equal` - bugfix in `CCArray.equal`
- fix `CCString.*_ascii`; add `CCChar.{upper,lower}case_ascii` - fix `CCString.*_ascii`; add `CCChar.{upper,lower}case_ascii`
@ -771,7 +216,7 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- more general types for `CCArray.{for_all2,exists2}` - more general types for `CCArray.{for_all2,exists2}`
- more general type for `CCResult.map_or` - more general type for `CCResult.map_or`
## 0.19 == 0.19
- add regression test for #75 - add regression test for #75
- Fix `CCString.Split.{left,right}` (#75) - Fix `CCString.Split.{left,right}` (#75)
@ -783,7 +228,7 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- add `CCstring.of_char` - add `CCstring.of_char`
- update headers - update headers
## 0.18 == 0.18
- update implem of `CCVector.equal` - update implem of `CCVector.equal`
- add `CCOpt.get_or` with label, deprecates `get` - add `CCOpt.get_or` with label, deprecates `get`
@ -794,13 +239,13 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- add `Lazy_list.filter` - add `Lazy_list.filter`
- add `CCList.range_by` - add `CCList.range_by`
## 0.17 == 0.17
### potentially breaking === potentially breaking
- change the semantics of `CCString.find_all` (allow overlaps) - change the semantics of `CCString.find_all` (allow overlaps)
### Additions === Additions
- add `CCString.pad` for more webscale - add `CCString.pad` for more webscale
- add `(--^)` to CCRAl, CCFQueue, CCKlist (closes #56); add `CCKList.Infix` - add `(--^)` to CCRAl, CCFQueue, CCKlist (closes #56); add `CCKList.Infix`
@ -829,7 +274,7 @@ See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.
- add `CCImmutArray` into containers.data - add `CCImmutArray` into containers.data
- add `CCList.Assoc.remove` - add `CCList.Assoc.remove`
### Fixes, misc === Fixes, misc
- Make `CCPersistentHashtbl.S.merge` more general. - Make `CCPersistentHashtbl.S.merge` more general.
- optimize KMP search in `CCString.Find` (hand-specialize code) - optimize KMP search in `CCString.Find` (hand-specialize code)
@ -847,9 +292,9 @@ others:
- add an `IO` section to the tutorial - add an `IO` section to the tutorial
- enable `-j 0` for ocamlbuild - enable `-j 0` for ocamlbuild
## 0.16 == 0.16
### breaking === breaking
- change the signature of `CCHeap.{of_gen,of_seq,of_klist}` - change the signature of `CCHeap.{of_gen,of_seq,of_klist}`
- change the API of `CCMixmap` - change the API of `CCMixmap`
@ -857,18 +302,18 @@ others:
- optional argument `~eq` to `CCGraph.Dot.pp` - optional argument `~eq` to `CCGraph.Dot.pp`
- rename `CCFuture` into `CCPool` - rename `CCFuture` into `CCPool`
### deprecations === deprecations
- deprecate `containers.bigarray` - deprecate `containers.bigarray`
- deprecate `CCHashtbl.{Counter,Default}` tables - deprecate `CCHashtbl.{Counter,Default}` tables
- deprecate `CCLinq` in favor of standalone `OLinq` (to be released) - deprecate `CCLinq` in favor of standalone `OLinq` (to be released)
### bugfixes === bugfixes
- fix wrong signature of `CCHashtbl.Make.{keys,values}_list` - fix wrong signature of `CCHashtbl.Make.{keys,values}_list`
- missing constraint in `CCSexpM.ID_MONAD` - missing constraint in `CCSexpM.ID_MONAD`
### new features === new features
- add a tutorial file - add a tutorial file
- add a printer into CCHeap - add a printer into CCHeap
@ -903,14 +348,14 @@ others:
- update `examples/id_sexp` so it can read on stdin - update `examples/id_sexp` so it can read on stdin
- add `CCList.fold_map2` - add `CCList.fold_map2`
## 0.15 == 0.15
### breaking changes === breaking changes
- remove deprecated `CCFloat.sign` - remove deprecated `CCFloat.sign`
- remove deprecated `CCSexpStream` - remove deprecated `CCSexpStream`
### other changes === other changes
- basic color handling in `CCFormat`, using tags and ANSI codes - basic color handling in `CCFormat`, using tags and ANSI codes
- add `CCVector.ro_vector` as a convenience alias - add `CCVector.ro_vector` as a convenience alias
@ -926,22 +371,22 @@ others:
- bugfix: forgot to export `{Set.Map}.OrderedType` in `Containers` - bugfix: forgot to export `{Set.Map}.OrderedType` in `Containers`
## 0.14 == 0.14
### breaking changes === breaking changes
- change the type `'a CCParse.t` with continuations - change the type `'a CCParse.t` with continuations
- add labels on `CCParse.parse_*` functions - add labels on `CCParse.parse_*` functions
- change semantics of `CCList.Zipper.is_empty` - change semantics of `CCList.Zipper.is_empty`
### other changes === other changes
- deprecate `CCVector.rev'`, renamed into `CCVector.rev_in_place` - deprecate `CCVector.rev'`, renamed into `CCVector.rev_in_place`
- deprecate `CCVector.flat_map'`, renamed `flat_map_seq` - deprecate `CCVector.flat_map'`, renamed `flat_map_seq`
- add `CCMap.add_{list,seqe` - add `CCMap.add_{list,seqe`
- add `CCSet.add_{list,seq}` - add `CCSet.add_{list,seq}`
- fix small ugliness in `Map.print` and `Set.print` - fix small uglyness in `Map.print` and `Set.print`
- add `CCFormat.{ksprintf,string_quoted}` - add `CCFormat.{ksprintf,string_quoted}`
- add `CCArray.sort_generic` for sorting over array-like structures in place - add `CCArray.sort_generic` for sorting over array-like structures in place
- add `CCHashtbl.add` mimicking the stdlib `Hashtbl.add` - add `CCHashtbl.add` mimicking the stdlib `Hashtbl.add`
@ -981,9 +426,9 @@ others:
- bugfix in hashtable printing - bugfix in hashtable printing
- bugfix in `CCKList.take`, it was slightly too eager - bugfix in `CCKList.take`, it was slightly too eager
## 0.13 == 0.13
### Breaking changes === Breaking changes
- big refactoring of `CCLinq` (now simpler and cleaner) - big refactoring of `CCLinq` (now simpler and cleaner)
- changed the types `input` and `ParseError` in `CCParse` - changed the types `input` and `ParseError` in `CCParse`
@ -991,12 +436,12 @@ others:
- change the exceptions in `CCVector` - change the exceptions in `CCVector`
- change signature of `CCDeque.of_seq` - change signature of `CCDeque.of_seq`
### Other changes === Other changes
- add module `CCWBTree`, a weight-balanced tree, in `containers.data`. - add module `CCWBTree`, a weight-balanced tree, in `containers.data`.
- add module `CCBloom` in `containers.data`, a bloom filter - add module `CCBloom` in `containers.data`, a bloom filter
- new module `CCHashTrie` in `containers.data`, HAMT-like associative map - new module `CCHashTrie` in `containers.data`, HAMT-like associative map
- add module `CCBitField` in `containers.data`, a safe abstraction for bitfields of `< 62 bits` - add module `CCBitField` in `containers.data`, a safe abstraction for bitfields of < 62 bits
- add module `CCHashSet` into `containers.data`, a mutable set - add module `CCHashSet` into `containers.data`, a mutable set
- add module `CCInt64` - add module `CCInt64`
- move module `RAL` into `containers.data` as `CCRAL` - move module `RAL` into `containers.data` as `CCRAL`
@ -1043,14 +488,14 @@ others:
- new implementation for `CCDeque`, more efficient - new implementation for `CCDeque`, more efficient
- update makefile (target devel) - update makefile (target devel)
## 0.12 == 0.12
### breaking === breaking
- change type of `CCString.blit` so it writes into `Bytes.t` - change type of `CCString.blit` so it writes into `Bytes.t`
- better default opening flags for `CCIO.with_{in, out}` - better default opening flags for `CCIO.with_{in, out}`
### non-breaking === non-breaking
NOTE: use of `containers.io` is deprecated (its only module has moved to `containers`) NOTE: use of `containers.io` is deprecated (its only module has moved to `containers`)
@ -1071,7 +516,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- fix: use the proper array module in `CCRingBuffer` - fix: use the proper array module in `CCRingBuffer`
- bugfix: `CCRandom.float_range` - bugfix: `CCRandom.float_range`
## 0.11 == 0.11
- add `CCList.{remove,is_empty}` - add `CCList.{remove,is_empty}`
- add `CCOpt.is_none` - add `CCOpt.is_none`
@ -1093,7 +538,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- add `CCList.Set.{add,remove}` - add `CCList.Set.{add,remove}`
- fix doc of `CCstring.Split.list_` - fix doc of `CCstring.Split.list_`
## 0.10 == 0.10
- add `containers.misc.Puf.iter` - add `containers.misc.Puf.iter`
- add `CCString.{lines,unlines,concat_gen}` - add `CCString.{lines,unlines,concat_gen}`
@ -1108,7 +553,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- remove `containers.pervasives`, add the module `Containers` to core - remove `containers.pervasives`, add the module `Containers` to core
- bugfix in `CCFormat.to_file` - bugfix in `CCFormat.to_file`
## 0.9 == 0.9
- add `Float`, `Ref`, `Set`, `Format` to `CCPervasives` - add `Float`, `Ref`, `Set`, `Format` to `CCPervasives`
- `CCRingBuffer.append` (simple implementation) - `CCRingBuffer.append` (simple implementation)
@ -1128,7 +573,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- add `CCSet` module in core/ - add `CCSet` module in core/
- add `CCRef` module in core/ - add `CCRef` module in core/
## 0.8 == 0.8
- add `@Emm` to authors - add `@Emm` to authors
- refactored heavily `CCFuture` (much simpler, cleaner, basic API and thread pool) - refactored heavily `CCFuture` (much simpler, cleaner, basic API and thread pool)
@ -1136,7 +581,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- merged `test_levenshtein` with other tests - merged `test_levenshtein` with other tests
- Add experimental rose tree in `Containers_misc.RoseTree`. - Add experimental rose tree in `Containers_misc.RoseTree`.
- remove a lot of stuff from `containers.misc` (see `_oasis` for details) - remove a lot of stuff from `containers.misc` (see `_oasis` for details)
- `make devel` command, activating most flags, for developers (see #27) - `make devel` command, activating most flags, for developpers (see #27)
- use benchmark 1.4, with the upstreamed tree system - use benchmark 1.4, with the upstreamed tree system
- test `ccvector.iteri` - test `ccvector.iteri`
- add `CCFormat` into core/ - add `CCFormat` into core/
@ -1151,9 +596,9 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCHashtbl.{keys,values}_list` - `CCHashtbl.{keys,values}_list`
- more accurate type for `CCHashtbl.Make` - more accurate type for `CCHashtbl.Make`
## 0.7 == 0.7
### breaking === breaking
- remove `cgi`/ - remove `cgi`/
- removed useless Lwt-related module - removed useless Lwt-related module
@ -1161,11 +606,11 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- split the library into smaller pieces (with `containers.io`, `containers.iter`, - split the library into smaller pieces (with `containers.io`, `containers.iter`,
`containers.sexp`, `containers.data`) `containers.sexp`, `containers.data`)
### other changes === other changes
- cleanup: move sub-libraries to their own subdir each; mv everything into `src/` - cleanup: move sub-libraries to their own subdir each; mv everything into `src/`
- `sexp`: - `sexp`:
* `CCSexp` now split into `CCSexp` (manipulating expressions) and `CCSexpStream` * `CCSexp` now splitted into `CCSexp` (manipulating expressions) and `CCSexpStream`
* add `CCSexpM` for a simpler, monadic parser of S-expressions (deprecating `CCSexpStream`) * add `CCSexpM` for a simpler, monadic parser of S-expressions (deprecating `CCSexpStream`)
- `core`: - `core`:
* `CCString.fold` * `CCString.fold`
@ -1181,7 +626,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
* bugfix in `CCIO.read_all` and `CCIO.read_chunks` * bugfix in `CCIO.read_all` and `CCIO.read_chunks`
- use `-no-alias-deps` - use `-no-alias-deps`
## 0.6.1 == 0.6.1
- use subtree `gen/` for `CCGen` (symlink) rather than a copy. - use subtree `gen/` for `CCGen` (symlink) rather than a copy.
- Add benchmarks for the function `iter` of iterators. - Add benchmarks for the function `iter` of iterators.
@ -1189,14 +634,14 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCOpt.get_lazy` convenience function - `CCOpt.get_lazy` convenience function
- introduce `CCFloat`, add float functions to `CCRandom` (thanks to @struktured) - introduce `CCFloat`, add float functions to `CCRandom` (thanks to @struktured)
## 0.6 == 0.6
### breaking changes === breaking changes
- new `CCIO` module, much simpler, but incompatible interface - new `CCIO` module, much simpler, but incompatible interface
- renamed `CCIO` to `advanced.CCMonadIO` - renamed `CCIO` to `advanced.CCMonadIO`
### other changes === other changes
- `CCMultiSet.{add_mult,remove_mult,update}` - `CCMultiSet.{add_mult,remove_mult,update}`
- `CCVector.{top,top_exn}` - `CCVector.{top,top_exn}`
@ -1214,9 +659,9 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
are now tailrec are now tailrec
## 0.5 == 0.5
### breaking changes === breaking changes
- dependency on `cppo` (thanks to @whitequark, see `AUTHORS.md`) and `bytes` - dependency on `cppo` (thanks to @whitequark, see `AUTHORS.md`) and `bytes`
- `CCError`: - `CCError`:
@ -1225,7 +670,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCPervasives.Opt` -> `CCPervasives.Option` - `CCPervasives.Opt` -> `CCPervasives.Option`
- `Levenshtein.Index.remove` changed signature (useless param removed) - `Levenshtein.Index.remove` changed signature (useless param removed)
### other changes === other changes
- stronger inlining for `CCVector` (so that e.g. push is inline) - stronger inlining for `CCVector` (so that e.g. push is inline)
- more tests for `CCVector` - more tests for `CCVector`
@ -1240,7 +685,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- add Format printers to `CCString` - add Format printers to `CCString`
- `AUTHORS.md` - `AUTHORS.md`
## 0.4.1 == 0.4.1
- `CCOpt.get` - `CCOpt.get`
- new functions in `CCSexp.Traverse` - new functions in `CCSexp.Traverse`
@ -1249,7 +694,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- update of readme - update of readme
- generate doc for `containers.advanced` - generate doc for `containers.advanced`
## 0.4 == 0.4
- `core/CCSexp` for fast and lightweight S-expressions parsing/printing - `core/CCSexp` for fast and lightweight S-expressions parsing/printing
- moved `CCLinq`, `CCBatch` and `CCat` from core/ to advanced/ - moved `CCLinq`, `CCBatch` and `CCat` from core/ to advanced/
@ -1264,7 +709,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCPervasives` module, replacing modules of the standard library - `CCPervasives` module, replacing modules of the standard library
- removed type alias `CCString.t` (duplicate of String.t which already exists) - removed type alias `CCString.t` (duplicate of String.t which already exists)
## 0.3.4 == 0.3.4
- subtree for `sequence` repo - subtree for `sequence` repo
- `CCSequence` is now a copy of `sequence` - `CCSequence` is now a copy of `sequence`
@ -1274,7 +719,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- specialize some comparison functions - specialize some comparison functions
- `CCOrd.map` - `CCOrd.map`
## 0.3.3 == 0.3.3
- readme: add ci hook (to http://ci.cedeela.fr) - readme: add ci hook (to http://ci.cedeela.fr)
- `CCIO`: monad for IO actions-as-values - `CCIO`: monad for IO actions-as-values
@ -1294,7 +739,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCString.init` - `CCString.init`
- `CCError.fail_printf` - `CCError.fail_printf`
## 0.3.2 == 0.3.2
- small change in makefile - small change in makefile
- conversions for `CCString` - conversions for `CCString`
@ -1319,7 +764,7 @@ NOTE: use of `containers.io` is deprecated (its only module has moved to `contai
- `CCError.map2` - `CCError.map2`
- more combinators in `CCError` - more combinators in `CCError`
## 0.3.1 == 0.3.1
- test for `CCArray.shuffle` - test for `CCArray.shuffle`
- bugfix in `CCArray.shuffle` - bugfix in `CCArray.shuffle`

View file

@ -1,9 +0,0 @@
# Code of Conduct
This project has adopted the [OCaml Code of Conduct](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md).
# Enforcement
This project follows the OCaml Code of Conduct
[enforcement policy](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md#enforcement).
To report any violations, please contact @c-cube

32
HOWTO.adoc Normal file
View file

@ -0,0 +1,32 @@
= HOWTO
== Make a release
Beforehand, check `grep deprecated -r src` to see whether some functions
can be removed.
. `make test`
. update version in `_oasis`
. `make update_next_tag` (to update `@since` comments; be careful not to change symlinks)
. check status of modules (`{b status: foo}`) and update if required;
removed deprecated functions, etc.
. update `CHANGELOG.adoc` (see its end to find the right git command)
. commit the changes
. `git checkout stable`
. `git merge master`
. `oasis setup; make test doc`
. update `opam` (the version field; remove `oasis` in deps)
. tag, and push both to github
. `opam pin add containers https://github.com/c-cube/ocaml-containers.git#<release>`
. new opam package: `opam publish prepare; opam publish submit`
. re-generate doc: `make doc push_doc`
== List Authors
`git log --format='%aN' | sort -u`
== Subtree
If gen is https://github.com/c-cube/gen.git[this remote]:
git subtree pull --prefix gen gen master --squash

140
Makefile
View file

@ -1,44 +1,140 @@
PACKAGES=containers,containers-data # OASIS_START
# DO NOT EDIT (digest: a3c674b4239234cbbe53afe090018954)
all: build test SETUP = ocaml setup.ml
build: build: setup.data
dune build @install -p $(PACKAGES) $(SETUP) -build $(BUILDFLAGS)
test: build doc: setup.data build
# run tests in release mode to expose bug in #454 $(SETUP) -doc $(DOCFLAGS)
dune runtest --display=quiet --cache=disabled --no-buffer --force --profile=release
test: setup.data build
$(SETUP) -test $(TESTFLAGS)
all:
$(SETUP) -all $(ALLFLAGS)
install: setup.data
$(SETUP) -install $(INSTALLFLAGS)
uninstall: setup.data
$(SETUP) -uninstall $(UNINSTALLFLAGS)
reinstall: setup.data
$(SETUP) -reinstall $(REINSTALLFLAGS)
clean: clean:
dune clean $(SETUP) -clean $(CLEANFLAGS)
doc: distclean:
dune build @doc $(SETUP) -distclean $(DISTCLEANFLAGS)
examples: setup.data:
dune build examples/id_sexp.exe $(SETUP) -configure $(CONFIGUREFLAGS)
format: configure:
@dune build $(DUNE_OPTS) @fmt --auto-promote $(SETUP) -configure $(CONFIGUREFLAGS)
format-check: .PHONY: build doc test all install uninstall reinstall clean distclean configure
@dune build $(DUNE_OPTS) @fmt --display=quiet
# OASIS_STOP
VERSION=$(shell awk '/^version:/ {print $$2}' containers.opam) EXAMPLES = examples/mem_size.native examples/collatz.native \
examples/bencode_write.native # examples/crawl.native
OPTIONS = -use-ocamlfind -I _build
examples: all
ocamlbuild $(OPTIONS) -package unix -I . $(EXAMPLES)
push_doc: doc
rsync -tavu containers.docdir/* cedeela.fr:~/simon/root/software/containers/
push_doc_gh: doc
git checkout gh-pages && \
rm -rf dev/ && \
mkdir -p dev && \
cp -r containers.docdir/* dev/ && \
git add --all dev
DONTTEST=myocamlbuild.ml setup.ml $(wildcard src/**/*.cppo.*) $(wildcard src/**/*Labels*)
QTESTABLE=$(filter-out $(DONTTEST), \
$(wildcard src/core/*.ml) \
$(wildcard src/core/*.mli) \
$(wildcard src/data/*.ml) \
$(wildcard src/data/*.mli) \
$(wildcard src/string/*.ml) \
$(wildcard src/string/*.mli) \
$(wildcard src/unix/*.ml) \
$(wildcard src/unix/*.mli) \
$(wildcard src/sexp/*.ml) \
$(wildcard src/sexp/*.mli) \
$(wildcard src/iter/*.ml) \
$(wildcard src/iter/*.mli) \
$(wildcard src/bigarray/*.ml) \
$(wildcard src/bigarray/*.mli) \
$(wildcard src/threads/*.ml) \
$(wildcard src/threads/*.mli) \
)
qtest-clean:
@rm -rf qtest/
QTEST_PREAMBLE='open CCFun;; '
#qtest-build: qtest-clean build
# @mkdir -p qtest
# @qtest extract --preamble $(QTEST_PREAMBLE) \
# -o qtest/qtest_all.ml \
# $(QTESTABLE) 2> /dev/null
# @ocamlbuild $(OPTIONS) -pkg oUnit,QTest2Lib,ocamlbuildlib \
# -I core -I misc -I string \
# qtest/qtest_all.native
qtest-gen:
@mkdir -p qtest
@if which qtest > /dev/null ; then \
qtest extract --preamble $(QTEST_PREAMBLE) \
-o qtest/run_qtest.ml \
$(QTESTABLE) 2> /dev/null ; \
else touch qtest/run_qtest.ml ; \
fi
push-stable:
git checkout stable
git merge master -m 'merge from master'
oasis setup
git commit -a -m 'oasis files'
git push origin
git checkout master
clean-generated:
rm **/*.{mldylib,mlpack,mllib} myocamlbuild.ml -f
tags:
otags *.ml *.mli
VERSION=$(shell awk '/^Version:/ {print $$2}' _oasis)
update_next_tag: update_next_tag:
@echo "update version to $(VERSION)..." @echo "update version to $(VERSION)..."
sed -i "s/NEXT_VERSION/$(VERSION)/g" $(wildcard src/**/*.ml) $(wildcard src/**/*.mli) zsh -c 'sed -i "s/NEXT_VERSION/$(VERSION)/g" **/*.ml **/*.mli'
sed -i "s/NEXT_RELEASE/$(VERSION)/g" $(wildcard src/**/*.ml) $(wildcard src/**/*.mli) zsh -c 'sed -i "s/NEXT_RELEASE/$(VERSION)/g" **/*.ml **/*.mli'
devel:
./configure --enable-bench --enable-tests --enable-unix \
--enable-thread
make all
WATCH?=@src/check @tests/runtest
watch: watch:
@dune build $(WATCH) -w while find src/ benchs/ -print0 | xargs -0 inotifywait -e delete_self -e modify ; do \
echo "============ at `date` ==========" ; \
make all; \
done
reindent: reindent:
@which ocp-indent || ( echo "require ocp-indent" ; exit 1 ) @which ocp-indent || ( echo "require ocp-indent" ; exit 1 )
@find src '(' -name '*.ml' -or -name '*.mli' ')' -type f -print0 | xargs -0 echo "reindenting: " @find src '(' -name '*.ml' -or -name '*.mli' ')' -type f -print0 | xargs -0 echo "reindenting: "
@find src '(' -name '*.ml' -or -name '*.mli' ')' -type f -print0 | xargs -0 ocp-indent -i @find src '(' -name '*.ml' -or -name '*.mli' ')' -type f -print0 | xargs -0 ocp-indent -i
.PHONY: all test clean build doc update_next_tag watch examples .PHONY: examples push_doc tags qtest-gen qtest-clean devel update_next_tag

478
README.adoc Normal file
View file

@ -0,0 +1,478 @@
= OCaml-containers =
:toc: macro
:source-highlighter: pygments
A modular, clean and powerful extension of the OCaml standard library.
https://c-cube.github.io/ocaml-containers/last/[(Jump to the current API documentation)].
Containers is an extension of OCaml's standard library (under BSD license)
focused on data structures, combinators and iterators, without dependencies on
unix, str or num. Every module is independent and is prefixed with 'CC' in the
global namespace. Some modules extend the stdlib (e.g. CCList provides safe
map/fold_right/append, and additional functions on lists).
Alternatively, `open Containers` will bring enhanced versions of the standard
modules into scope.
image::https://travis-ci.org/c-cube/ocaml-containers.svg?branch=master[alt="Build Status", link="https://travis-ci.org/c-cube/ocaml-containers"]
toc::[]
== Quick Summary
Containers is:
- A usable, reasonably well-designed library that extends OCaml's standard
library (in 'src/core/', packaged under `containers` in ocamlfind. Modules
are totally independent and are prefixed with `CC` (for "containers-core"
or "companion-cube" because I'm a megalomaniac). This part should be
usable and should work. For instance, `CCList` contains functions and
lists including safe versions of `map` and `append`. It also
provides a drop-in replacement to the standard library, in the module
`Containers` (intended to be opened, replaces some stdlib modules
with extended ones).
- Several small additional libraries that complement it:
* `containers.data` with additional data structures that don't have an
equivalent in the standard library;
* `containers.iter` with list-like and tree-like iterators;
- Utilities around the `unix` library in `containers.unix` (mainly to spawn
sub-processes easily and deal with resources safely)
- A lightweight S-expression printer and streaming parser in `containers.sexp`
- A library for threaded programming in `containers.thread`,
including a blocking queue, semaphores, an extension of `Mutex`, and
thread-pool based futures.
Some of the modules have been moved to their own repository (e.g. `sequence`,
`gen`, `qcheck`) and are on opam for great fun and profit.
== Change Log
See link:CHANGELOG.adoc[this file].
== Finding help
- http://lists.ocaml.org/listinfo/containers-users[Mailing List]
the address is mailto:containers-users@lists.ocaml.org[]
- the https://github.com/c-cube/ocaml-containers/wiki[github wiki]
- on IRC, ask `companion_cube` on `#ocaml@freenode.net`
- image:https://badges.gitter.im/Join%20Chat.svg[alt="Gitter", link="https://gitter.im/c-cube/ocaml-containers?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"]
== Use
You might start with the <<tutorial>> to get a picture of how to use the library.
You can either build and install the library (see <<build>>), or just copy
files to your own project. The last solution has the benefits that you
don't have additional dependencies nor build complications (and it may enable
more inlining). Since modules have a friendly license and are mostly
independent, both options are easy.
In a toplevel, using ocamlfind:
[source,OCaml]
----
# #use "topfind";;
# #require "containers";;
# CCList.flat_map;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
# open Containers;; (* optional *)
# List.flat_map ;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
----
If you have comments, requests, or bugfixes, please share them! :-)
== License
This code is free, under the BSD license.
== Contents
See http://c-cube.github.io/ocaml-containers/[the documentation]
and <<tutorial,the tutorial below>> for a gentle introduction.
== Documentation
In general, see http://c-cube.github.io/ocaml-containers/last/ or
http://cedeela.fr/~simon/software/containers for the **API documentation**.
Some examples can be found link:doc/containers.adoc[there],
per-version doc http://c-cube.github.io/ocaml-containers/[there].
[[build]]
== Build
You will need OCaml `>=` 4.01.0.
=== Via opam
The prefered way to install is through http://opam.ocaml.org/[opam].
$ opam install containers
=== From Sources
On the branch `master` you will need `oasis` to build the library. On the
branch `stable` it is not necessary.
$ make
To build and run tests (requires `oUnit` and https://github.com/vincent-hugot/iTeML[qtest]):
$ opam install oUnit qtest
$ ./configure --enable-tests --enable-unix
$ make test
To build the small benchmarking suite (requires https://github.com/chris00/ocaml-benchmark[benchmark]):
$ opam install benchmark
$ make bench
$ ./benchs.native
== Contributing
PRs on github are very welcome (patches by email too, if you prefer so).
[[first-time-contribute]]
=== First-Time Contributors
Assuming your are in a clone of the repository:
. Some dependencies are required, you'll need
`opam install benchmark qcheck qtest sequence`.
. run `make devel` to enable everything (including tests).
. make your changes, commit, push, and open a PR.
. use `make test` without moderation! It must pass before a PR
is merged. There are around 900 tests right now, and new
features should come with their own tests.
If you feel like writing new tests, that is totally worth a PR
(and my gratefulness).
=== General Guidelines
A few guidelines to follow the philosophy of containers:
- no dependencies between basic modules (even just for signatures);
- add `@since` tags for new functions;
- add tests if possible (using https://github.com/vincent-hugot/iTeML/[qtest]). There are numerous inline tests already,
to see what it looks like search for comments starting with `(*$`
in source files.
=== For Total Beginners
Thanks for wanting to contribute!
To contribute a change, here are the steps (roughly):
. click "fork" on https://github.com/c-cube/ocaml-containers on the top right of the page. This will create a copy of the repository on your own github account.
. click the big green "clone or download" button, with "SSH". Copy the URL (which should look like `git@github.com:<your username>/ocaml-containers.git`) into a terminal to enter the command:
+
[source,sh]
----
$ git clone git@github.com:<your username>/ocaml-containers.git
----
+
. then, `cd` into the newly created directory.
. make the changes you want. See <<first-time-contribute>> for
more details about what to do in particular.
. use `git add` and `git commit` to commit these changes.
. `git push origin master` to push the new change(s) onto your
copy of the repository
. on github, open a "pull request" (PR). Et voilà !
[[tutorial]]
== Tutorial
This tutorial contains a few examples to illustrate the features and
usage of containers. We assume containers is installed and that
the library is loaded, e.g. with:
[source,OCaml]
----
#require "containers";;
----
=== Basics
We will start with a few list helpers, then look at other parts of
the library, including printers, maps, etc.
[source,OCaml]
----
(* quick reminder of this awesome standard operator *)
# (|>) ;;
- : 'a -> ('a -> 'b) -> 'b = <fun>
# open CCList.Infix;;
# let l = 1 -- 100;;
val l : int list = [1; 2; .....]
# l
|> CCList.filter_map
(fun x-> if x mod 3=0 then Some (float x) else None)
|> CCList.take 5 ;;
- : float list = [3.; 6.; 9.; 12.; 15.]
# let l2 = l |> CCList.take_while (fun x -> x<10) ;;
val l2 : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
(* an extension of Map.Make, compatible with Map.Make(CCInt) *)
# module IntMap = CCMap.Make(CCInt);;
(* conversions using the "sequence" type, fast iterators that are
pervasively used in containers. Combinators can be found
in the opam library "sequence". *)
# let map =
l2
|> List.map (fun x -> x, string_of_int x)
|> CCList.to_seq
|> IntMap.of_seq;;
val map : string CCIntMap.t = <abstr>
(* check the type *)
# CCList.to_seq ;;
- : 'a list -> 'a sequence = <fun>
# IntMap.of_seq ;;
- : (int * 'a) CCMap.sequence -> 'a IntMap.t = <fun>
(* we can print, too *)
# Format.printf "@[<2>map =@ @[<hov>%a@]@]@."
(IntMap.print CCFormat.int CCFormat.string_quoted)
map;;
map =
[1 --> "1", 2 --> "2", 3 --> "3", 4 --> "4", 5 --> "5", 6 --> "6",
7 --> "7", 8 --> "8", 9 --> "9"]
- : unit = ()
(* options are good *)
# IntMap.get 3 map |> CCOpt.map (fun s->s ^ s);;
- : string option = Some "33"
----
=== New types: `CCVector`, `CCHeap`, `CCResult`
Containers also contains (!) a few datatypes that are not from the standard
library but that are useful in a lot of situations:
CCVector::
A resizable array, with a mutability parameter. A value of type
`('a, CCVector.ro) CCVector.t` is an immutable vector of values of type `'a`,
whereas a `('a, CCVector.rw) CCVector.t` is a mutable vector that
can be modified. This way, vectors can be used in a quite functional
way, using operations such as `map` or `flat_map`, or in a more
imperative way.
CCHeap::
A priority queue (currently, leftist heaps) functorized over
a module `sig val t val leq : t -> t -> bool` that provides a type `t`
and a partial order `leq` on `t`.
CCResult::
An error type for making error handling more explicit (an error monad,
really, if you're not afraid of the "M"-word).
Subsumes and replaces the old `CCError`.
It uses the new `result` type from the standard library (or from
the retrocompatibility package on opam) and provides
many combinators for dealing with `result`.
Now for a few examples:
[source,OCaml]
----
(* create a new empty vector. It is mutable, for otherwise it would
not be very useful. *)
# CCVector.create;;
- : unit -> ('a, CCVector.rw) CCVector.t = <fun>
(* init, similar to Array.init, can be used to produce a
vector that is mutable OR immutable (see the 'mut parameter?) *)
# CCVector.init ;;
- : int -> (int -> 'a) -> ('a, 'mut) CCVector.t = <fun>c
(* use the infix (--) operator for creating a range. Notice
that v is a vector of integer but its mutability is not
decided yet. *)
# let v = CCVector.(1 -- 10);;
val v : (int, '_a) CCVector.t = <abstr>
# Format.printf "v = @[%a@]@." (CCVector.print CCInt.print) v;;
v = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(* now let's mutate v *)
# CCVector.push v 42;;
- : unit = ()
(* now v is a mutable vector *)
# v;;
- : (int, CCVector.rw) CCVector.t = <abstr>
(* functional combinators! *)
# let v2 = v
|> CCVector.map (fun x-> x+1)
|> CCVector.filter (fun x-> x mod 2=0)
|> CCVector.rev ;;
val v2 : (int, '_a) CCVector.t = <abstr>
# Format.printf "v2 = @[%a@]@." (CCVector.print CCInt.print) v2;;
v2 = [10, 8, 6, 4, 2]
(* let's transfer to a heap *)
# module IntHeap = CCHeap.Make(struct type t = int let leq = (<=) end);;
# let h = v2 |> CCVector.to_seq |> IntHeap.of_seq ;;
val h : IntHeap.t = <abstr>
(* We can print the content of h
(printing is not necessarily in order, though) *)
# Format.printf "h = [@[%a@]]@." (IntHeap.print CCInt.print) h;;
h = [2,4,6,8,10]
(* we can remove the first element, which also returns a new heap
that does not contain it — CCHeap is a functional data structure *)
# IntHeap.take h;;
- : (IntHeap.t * int) option = Some (<abstr>, 2)
# let h', x = IntHeap.take_exn h ;;
val h' : IntHeap.t = <abstr>
val x : int = 2
(* see, 2 is removed *)
# IntHeap.to_list h' ;;
- : int list = [4; 6; 8; 10]
----
=== IO helpers
The core library contains a module called `CCIO` that provides useful
functions for reading and writing files. It provides functions that
make resource handling easy, following
the pattern `with_resource : resource -> (access -> 'a) -> 'a` where
the type `access` is a temporary handle to the resource (e.g.,
imagine `resource` is a file name and `access` a file descriptor).
Calling `with_resource r f` will access `r`, give the result to `f`,
compute the result of `f` and, whether `f` succeeds or raises an
error, it will free the resource.
Consider for instance:
[source,OCaml]
----
# CCIO.with_out "/tmp/foobar"
(fun out_channel ->
CCIO.write_lines_l out_channel ["hello"; "world"]);;
- : unit = ()
----
This just opened the file '/tmp/foobar', creating it if it didn't exist,
and wrote two lines in it. We did not have to close the file descriptor
because `with_out` took care of it. By the way, the type signatures are:
[source,OCaml]
----
val with_out :
?mode:int -> ?flags:open_flag list ->
string -> (out_channel -> 'a) -> 'a
val write_lines_l : out_channel -> string list -> unit
----
So we see the pattern for `with_out` (which opens a function in write
mode and gives its functional argument the corresponding file descriptor).
NOTE: you should never let the resource escape the
scope of the `with_resource` call, because it will not be valid outside.
OCaml's type system doesn't make it easy to forbid that so we rely
on convention here (it would be possible, but cumbersome, using
a record with an explicitely quantified function type).
Now we can read the file again:
[source,OCaml]
----
# let lines = CCIO.with_in "/tmp/foobar" CCIO.read_lines_l ;;
val lines : string list = ["hello"; "world"]
----
There are some other functions in `CCIO` that return _generators_
instead of lists. The type of generators in containers
is `type 'a gen = unit -> 'a option` (combinators can be
found in the opam library called "gen"). A generator is to be called
to obtain successive values, until it returns `None` (which means it
has been exhausted). In particular, python users might recognize
the function
[source,OCaml]
----
# CCIO.File.walk ;;
- : string -> walk_item gen = <fun>;;
----
where `type walk_item = [ `Dir | `File ] * string` is a path
paired with a flag distinguishing files from directories.
=== To go further: containers.data
There is also a sub-library called `containers.data`, with lots of
more specialized data-structures.
The documentation contains the API for all the modules
(see link:README.adoc[the readme]); they also provide
interface to `sequence` and, as the rest of containers, minimize
dependencies over other modules. To use `containers.data` you need to link it,
either in your build system or by `#require containers.data;;`
A quick example based on purely functional double-ended queues:
[source,OCaml]
----
# #require "containers.data";;
# #install_printer CCFQueue.print;; (* better printing of queues! *)
# let q = CCFQueue.of_list [2;3;4] ;;
val q : int CCFQueue.t = queue {2; 3; 4}
# let q2 = q |> CCFQueue.cons 1 |> CCFQueue.cons 0 ;;
val q2 : int CCFQueue.t = queue {0; 1; 2; 3; 4}
(* remove first element *)
# CCFQueue.take_front q2;;
- : (int * int CCFQueue.t) option = Some (0, queue {1; 2; 3; 4})
(* q was not changed *)
# CCFQueue.take_front q;;
- : (int * int CCFQueue.t) option = Some (2, queue {3; 4})
(* take works on both ends of the queue *)
# CCFQueue.take_back_l 2 q2;;
- : int CCFQueue.t * int list = (queue {0; 1; 2}, [3; 4])
----
=== Common Type Definitions
Some structural types are used throughout the library:
gen:: `'a gen = unit -> 'a option` is an iterator type. Many combinators
are defined in the opam library https://github.com/c-cube/gen[gen]
sequence:: `'a sequence = (unit -> 'a) -> unit` is also an iterator type.
It is easier to define on data structures than `gen`, but it a bit less
powerful. The opam library https://github.com/c-cube/sequence[sequence]
can be used to consume and produce values of this type.
error:: `'a or_error = ('a, string) result = Error of string | Ok of 'a`
using the standard `result` type, supported in `CCResult`.
klist:: `'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]` is a lazy list
without memoization, used as a persistent iterator. The reference
module is `CCKList` (in `containers.iter`).
printer:: `'a printer = Format.formatter -> 'a -> unit` is a pretty-printer
to be used with the standard module `Format`. In particular, in many cases,
`"foo: %a" Foo.print foo` will type-check.
=== Extended Documentation
See link:doc/containers.adoc[the extended documentation] for more examples.
Powered by image:http://oasis.forge.ocamlcore.org/oasis-badge.png[alt="OASIS", style="border: none;", link="http://oasis.forge.ocamlcore.org/"]

699
README.md
View file

@ -1,699 +0,0 @@
# OCaml-containers 📦 [![Build and test](https://github.com/c-cube/ocaml-containers/actions/workflows/main.yml/badge.svg)](https://github.com/c-cube/ocaml-containers/actions/workflows/main.yml)
A modular, clean and powerful extension of the OCaml standard library.
[(Jump to the current API documentation)](https://c-cube.github.io/ocaml-containers/)
Containers is an extension of OCaml's standard library (under BSD license)
focused on data structures, combinators and iterators, without dependencies on
unix, str or num. Every module is independent and is prefixed with 'CC' in the
global namespace. Some modules extend the stdlib (e.g. `CCList` provides safe
`map`/`fold_right`/`append`, and additional functions on lists).
Alternatively, `open Containers` will bring enhanced versions of the standard
modules into scope.
## Quick Summary
Containers is:
- A usable, reasonably well-designed library that extends OCaml's standard
library (in 'src/core/', packaged under `containers` in ocamlfind. Modules
are totally independent and are prefixed with `CC` (for "containers-core"
or "companion-cube" because I'm a megalomaniac). This part should be
usable and should work. For instance, `CCList` contains functions and
lists including safe versions of `map` and `append`. It also
provides a drop-in replacement to the standard library, in the module
`Containers` (intended to be opened, replaces some stdlib modules
with extended ones), and a small S-expression printer and parser
that can be functorized over the representation of values.
- Some sub-libraries with a specific focus each:
* Utilities around the `unix` library in `containers.unix` (mainly to spawn
sub-processes easily and deal with resources safely)
* A bencode codec in `containers.bencode`. This is a tiny json-like
serialization format that is extremely simple. It comes from bittorrent files.
* A [CBOR](https://cbor.io) codec in `containers.cbor`. This is a
compact binary serialization format.
* The [Strongly Connected Component](https://en.wikipedia.org/wiki/Strongly_connected_component)
algorithm, functorized, in `containers.scc`
- A separate library `containers-data` with additional
data structures that don't have an equivalent in the standard library,
typically not as thoroughly maintained. This is now in its own package
since 3.0.
Some of the modules have been moved to their own repository (e.g. `sequence` (now `iter`),
`gen`, `qcheck`) and are on opam for great fun and profit.
Containers-thread has been removed in favor of [Moonpool](https://github.com/c-cube/moonpool/).
## Migration Guide
### To 3.0
The [changelog's breaking section](CHANGELOG.md) contains a list of the breaking
changes in this release.
1. The biggest change is that some sub-libraries have been either turned into
their own packages (`containers-data`),
deleted (`containers.iter`),or merged elsewhere (`containers.sexp`).
This means that if use these libraries you will have to edit your
`dune`/`_oasis`/`opam` files.
- if you use `containers.sexp` (i.e. the `CCSexp` module), it now lives in
`containers` itself.
- if you used anything in `containers.data`, you need to depend on the
`containers-data` package now.
2. Another large change is the removal (at last!) of functions deprecated
in 2.8, related to the spread of `Seq.t` as the standard iterator type.
Functions like `CCVector.of_seq` now operate on this standard `Seq.t` type,
and old-time iteration based on [iter](https://github.com/c-cube/iter)
is now named `of_iter`, `to_iter`, etc.
Here you need to change your code, possibly using search and replace.
Thankfully, the typechecker should guide you.
3. `Array_slice` and `String.Sub` have been removed to simplify the
code and `String` more lightweight. There is no replacement at the moment.
Please tell us if you need this to be turned into a sub-library.
4. Renaming of some functions into more explicit/clear names.
Examples:
* `CCVector.shrink` is now `CCVector.truncate`
* `CCVector.remove` is now `CCVector.remove_unordered`, to be
contrasted with the new `CCVector.remove_and_shift`.
* `CCPair.map_fst` and `map_snd` now transform a tuple into another tuple
by modify the first (resp. second) element.
5. All the collection pretty-printers now take their separator/start/stop
optional arguments as `unit printer` (i.e. `Format.formatter -> unit -> unit`
functions) rather than strings. This gives the caller better control
over the formatting of lists, arrays, queues, tables, etc.
6. Removal of many deprecated functions.
### To 2.0
- The type system should detect issues related to `print` renamed into `pp` easily.
If you are lucky, a call to `sed -i 's/print/pp/g'` on the concerned files
might help rename all the calls
properly.
- many optional arguments have become mandatory, because their default value
would be a polymorphic "magic" operator such as `(=)` or `(>=)`.
Now these have to be specified explicitly, but during the transition
you can use `Stdlib.(=)` and `Stdlib.(>=)` as explicit arguments.
- if your code contains `open Containers`, the biggest hurdle you face
might be that operators have become monomorphic by default.
We believe this is a useful change that prevents many subtle bugs.
However, during migration and until you use proper combinators for
equality (`CCEqual`), comparison (`CCOrd`), and hashing (`CCHash`),
you might want to add `open Stdlib` just after the `open Containers`.
See [the section on monomorphic operators](#monomorphic-operators-why-and-how) for more details.
## Monomorphic operators: why, and how?
### Why shadow polymorphic operators by default?
To quote @bluddy in [#196](https://github.com/c-cube/ocaml-containers/issues/196):
The main problem with polymorphic comparison is that many data structures will
give one result for structural comparison, and a different result for semantic
comparison. The classic example is comparing maps. If you have a list of maps
and try to use comparison to sort them, you'll get the wrong result: multiple
map structures can represent the same semantic mapping from key to value, and
comparing them in terms of structure is simply wrong. A far more pernicious bug
occurs with hashtables. Identical hashtables will seem to be identical for a
while, as before they've had a key clash, the outer array is likely to be the
same. Once you get a key clash though, you start getting lists inside the
arrays (or maps inside the arrays if you try to make a smarter hashtable) and
that will cause comparison errors ie. identical hashtables will be seen as
different or vice versa.
Every time you use a polymorphic comparison where you're using a data type
where structural comparison != semantic comparison, it's a bug. And every time
you use polymorphic comparison where the type of data being compared may vary
(e.g. it's an int now, but it may be a map later), you're planting a bug for
the future.
See also:
- https://blog.janestreet.com/the-perils-of-polymorphic-compare/
- https://blog.janestreet.com/building-a-better-compare/
### Sometimes polymorphic operators still make sense!
If you just want to use polymorphic operators, it's fine! You can access them
easily by using `Stdlib.(=)`, `Stdlib.max`, etc.
When migrating a module, you can add `open Stdlib` on top of it to restore
the default behavior. It is, however, recommended to export an `equal` function
(and `compare`, and `hash`) for all the public types, even if their internal
definition is just the corresponding polymorphic operator.
This way, other modules can refer to `Foo.equal` and will not have to be
updated the day `Foo.equal` is no longer just polymorphic equality.
Another bonus is that `Hashtbl.Make(Foo)` or `Map.Make(Foo)` will just work™.
### Further discussions
See issues
[#196](https://github.com/c-cube/ocaml-containers/issues/196),
[#197](https://github.com/c-cube/ocaml-containers/issues/197)
## Debugging with `ocamldebug`
To print values with types defined in `containers` in the bytecode debugger,
you first have to load the appropriate bytecode archives. After starting a
session, e.g. `ocamldebug your_program.bc`,
```ocaml non-deterministic=command
# #load_printer containers_monomorphic.cma;;
# #load_printer containers.cma;;
```
For these archives to be found, you may have to `run` the program first. Now
printing functions that have the appropriate type `Format.formatter -> 'a ->
unit` can be installed. For example,
```ocaml non-deterministic=command
# #install_printer Containers.Int.pp;;
```
However, printer combinators are not easily handled by `ocamldebug`. For
instance `# install_printer Containers.(List.pp Int.pp)` will *not* work out of
the box. You can make this work by writing a short module which defines
ready-made combined printing functions, and loading that in ocamldebug. For
instance
```ocaml non-deterministic=command
module M = struct
let pp_int_list = Containers.(List.pp Int.pp)
end;;
```
loaded via `# load_printer m.cmo` and installed as `# install_printer
M.pp_int_list`.
## Change Log
See [this file](./CHANGELOG.md).
## Finding help
- [Mailing List](http://lists.ocaml.org/listinfo/containers-users)
the address is <mailto:containers-users@lists.ocaml.org>
- the [github wiki](https://github.com/c-cube/ocaml-containers/wiki)
- on IRC, ask `companion_cube` on `#ocaml@irc.libera.chat`
- there is a `#containers` channel on OCaml's discord server.
## Use
You might start with the [tutorial](#tutorial) to get a picture of how to use the library.
You can either build and install the library (see [build](#build)), or just copy
files to your own project. The last solution has the benefits that you
don't have additional dependencies nor build complications (and it may enable
more inlining). Since modules have a friendly license and are mostly
independent, both options are easy.
In a toplevel, using ocamlfind:
```ocaml
# #use "topfind";;
...
# #require "containers";;
# #require "containers-data";;
# CCList.flat_map;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
# open Containers (* optional *);;
# List.flat_map ;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
```
If you have comments, requests, or bugfixes, please share them! :-)
## License
This code is free, under the BSD license.
## Contents
See [the documentation](http://c-cube.github.io/ocaml-containers/)
and [the tutorial below](#tutorial) for a gentle introduction.
## Documentation
In general, see http://c-cube.github.io/ocaml-containers/last/ for the **API documentation**.
Some examples can be found [there](doc/containers.md),
per-version doc [there](http://c-cube.github.io/ocaml-containers/).
## Build
You will need OCaml `>=` 4.03.0.
### Via opam
The preferred way to install is through [opam](http://opam.ocaml.org/).
```
$ opam install containers
```
### From Sources
<details>
You need dune (formerly jbuilder).
```
$ make
```
To build and run tests (requires `qcheck-core`, `gen`, `iter`):
```
$ opam install qcheck-core
$ make test
```
To build the small benchmarking suite (requires [benchmark](https://github.com/chris00/ocaml-benchmark)):
```
$ opam install benchmark batteries
$ make bench
$ ./benchs/run_benchs.sh
```
</details>
## Contributing
PRs on github are very welcome (patches by email too, if you prefer so).
<details>
<summary>how to contribute (click to unfold)</summary>
### List of authors
The list of contributors can be seen [on github](https://github.com/c-cube/ocaml-containers/graphs/contributors).
Alternatively, `git authors` from git-extras can be invoked from within the repo
to list authors based on the git commits.
### First-Time Contributors
Assuming your are in a clone of the repository:
1. Some dependencies are required, you'll need
`opam install benchmark qcheck-core iter gen mdx uutf yojson`.
2. run `make all` to enable everything (including tests).
3. make your changes, commit, push, and open a PR.
4. use `make test` without moderation! It must pass before a PR
is merged. There are around 1150 tests right now, and new
features should come with their own tests.
If you feel like writing new tests, that is totally worth a PR
(and my gratefulness).
### General Guidelines
A few guidelines to follow the philosophy of containers:
- no dependencies between basic modules (even just for signatures);
- add `@since` tags for new functions;
- add tests if possible (see `tests/` dir)
There are numerous inline tests already,
to see what it looks like search for comments starting with `(*$`
in source files.
### For Total Beginners
Thanks for wanting to contribute!
To contribute a change, here are the steps (roughly):
1. click "fork" on https://github.com/c-cube/ocaml-containers on the top right of the page. This will create a copy of the repository on your own github account.
2. click the big green "clone or download" button, with "SSH". Copy the URL (which should look like `git@github.com:<your username>/ocaml-containers.git`) into a terminal to enter the command:
```
$ git clone git@github.com:<your username>/ocaml-containers.git
```
3. then, `cd` into the newly created directory.
4. make the changes you want. See <#first-time-contributors> for
more details about what to do in particular.
5. use `git add` and `git commit` to commit these changes.
6. `git push origin master` to push the new change(s) onto your
copy of the repository
7. on github, open a "pull request" (PR). Et voilà !
</details>
## Tutorial
This tutorial contains a few examples to illustrate the features and
usage of containers.
<details>
<summary>an introduction to containers (click to unfold)</summary>
We assume containers is installed and that
the library is loaded, e.g. with:
```ocaml
# #require "containers";;
# Format.set_margin 50 (* for readability here *);;
- : unit = ()
```
### Basics
We will start with a few list helpers, then look at other parts of
the library, including printers, maps, etc.
```ocaml
# (|>) (* quick reminder of this awesome standard operator *);;
- : 'a -> ('a -> 'b) -> 'b = <fun>
# 10 |> succ;;
- : int = 11
# open CCList.Infix;;
# let l = 1 -- 100;;
val l : int list =
[1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21;
22; 23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39;
40; 41; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57;
58; 59; 60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75;
76; 77; 78; 79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93;
94; 95; 96; 97; 98; 99; 100]
# (* transform a list, dropping some elements *)
l
|> CCList.filter_map
(fun x-> if x mod 3=0 then Some (float x) else None)
|> CCList.take 5 ;;
- : float list = [3.; 6.; 9.; 12.; 15.]
# let l2 = l |> CCList.take_while (fun x -> x<10) ;;
val l2 : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
```
```ocaml
(* an extension of Map.Make, compatible with Map.Make(CCInt) *)
module IntMap = CCMap.Make(CCInt);;
```
```ocaml
# (* conversions using the "iter" type, fast iterators that are
pervasively used in containers. Combinators can be found
in the opam library "iter". *)
let map : string IntMap.t =
l2
|> List.map (fun x -> x, string_of_int x)
|> CCList.to_iter
|> IntMap.of_iter;;
val map : string IntMap.t = <abstr>
# CCList.to_iter (* check the type *);;
- : 'a list -> 'a CCList.iter = <fun>
# IntMap.of_iter ;;
- : (int * 'a) CCMap.iter -> 'a IntMap.t = <fun>
# (* we can print, too *)
Format.printf "@[<2>map =@ @[<hov>%a@]@]@."
(IntMap.pp CCFormat.int CCFormat.string_quoted)
map;;
map =
1 -> "1", 2 -> "2", 3 -> "3", 4 -> "4", 5
-> "5", 6 -> "6", 7 -> "7", 8 -> "8", 9 -> "9"
- : unit = ()
# (* options are good *)
IntMap.get 3 map |> CCOption.map (fun s->s ^ s);;
- : string option = Some "33"
```
### New types: `CCVector`, `CCHeap`, `CCResult`, `CCSexp`, `CCByte_buffer`
Containers also contains (!) a few datatypes that are not from the standard
library but that are useful in a lot of situations:
- `CCVector`:
A resizable array, with a mutability parameter. A value of type
`('a, CCVector.ro) CCVector.t` is an immutable vector of values of type `'a`,
whereas a `('a, CCVector.rw) CCVector.t` is a mutable vector that
can be modified. This way, vectors can be used in a quite functional
way, using operations such as `map` or `flat_map`, or in a more
imperative way.
- `CCHeap`:
A priority queue (currently, leftist heaps) functorized over
a module `sig val t val leq : t -> t -> bool` that provides a type `t`
and a partial order `leq` on `t`.
- `CCResult`
An error type for making error handling more explicit (an error monad,
really, if you're not afraid of the "M"-word).
Subsumes and replaces the old `CCError`.
It uses the new `result` type from the standard library (or from
the retrocompatibility package on opam) and provides
many combinators for dealing with `result`.
- `CCSexp` and `CCCanonical_sexp`:
functorized printer and parser for S-expressions, respectively as
actual S-expressions (like `sexplib`) and as canonical binary-safe
S-expressions (like `csexp`)
- `CCByte_buffer`: a better version of the standard `Buffer.t` which cannot be
extended and prevents access to its internal byte array. This type is
designed for (blocking) IOs and to produce complex strings incrementally
in an efficient way.
Now for a few examples:
```ocaml
# (* create a new empty vector. It is mutable, for otherwise it would
not be very useful. *)
CCVector.create;;
- : unit -> ('a, CCVector.rw) CCVector.t = <fun>
# (* init, similar to Array.init, can be used to produce a
vector that is mutable OR immutable (see the 'mut parameter?) *)
CCVector.init ;;
- : int -> (int -> 'a) -> ('a, 'mut) CCVector.t = <fun>
```
```ocaml non-deterministic=output
# (* use the infix (--) operator for creating a range. Notice
that v is a vector of integer but its mutability is not
decided yet. *)
let v = CCVector.(1 -- 10);;
val v : (int, '_a) CCVector.t = <abstr>
```
```ocaml
# Format.printf "v = @[%a@]@." (CCVector.pp CCInt.pp) v;;
v = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
- : unit = ()
# CCVector.push v 42;;
- : unit = ()
# v (* now v is a mutable vector *);;
- : (int, CCVector.rw) CCVector.t = <abstr>
# (* functional combinators! *)
let v2 : _ CCVector.ro_vector = v
|> CCVector.map (fun x-> x+1)
|> CCVector.filter (fun x-> x mod 2=0)
|> CCVector.rev ;;
val v2 : int CCVector.ro_vector = <abstr>
# Format.printf "v2 = @[%a@]@." (CCVector.pp CCInt.pp) v2;;
v2 = 10, 8, 6, 4, 2
- : unit = ()
```
```ocaml
(* let's transfer to a heap *)
module IntHeap = CCHeap.Make(struct type t = int let leq = (<=) end);;
```
```ocaml
# let h = v2 |> CCVector.to_iter |> IntHeap.of_iter ;;
val h : IntHeap.t = <abstr>
# (* We can print the content of h
(printing is not necessarily in order, though) *)
Format.printf "h = [@[%a@]]@." (IntHeap.pp CCInt.pp) h;;
h = [2,4,6,8,10]
- : unit = ()
# (* we can remove the first element, which also returns a new heap
that does not contain it — CCHeap is a functional data structure *)
IntHeap.take h;;
- : (IntHeap.t * int) option = Some (<abstr>, 2)
# let h', x = IntHeap.take_exn h ;;
val h' : IntHeap.t = <abstr>
val x : int = 2
# IntHeap.to_list h' (* see, 2 is removed *);;
- : int list = [4; 8; 10; 6]
```
### IO helpers
The core library contains a module called `CCIO` that provides useful
functions for reading and writing files. It provides functions that
make resource handling easy, following
the pattern `with_resource : resource -> (access -> 'a) -> 'a` where
the type `access` is a temporary handle to the resource (e.g.,
imagine `resource` is a file name and `access` a file descriptor).
Calling `with_resource r f` will access `r`, give the result to `f`,
compute the result of `f` and, whether `f` succeeds or raises an
error, it will free the resource.
Consider for instance:
```ocaml
# CCIO.with_out "./foobar"
(fun out_channel ->
CCIO.write_lines_l out_channel ["hello"; "world"]);;
- : unit = ()
```
This just opened the file 'foobar', creating it if it didn't exist,
and wrote two lines in it. We did not have to close the file descriptor
because `with_out` took care of it. By the way, the type signatures are:
```ocaml non-deterministic=command
val with_out :
?mode:int -> ?flags:open_flag list ->
string -> (out_channel -> 'a) -> 'a
val write_lines_l : out_channel -> string list -> unit
```
So we see the pattern for `with_out` (which opens a function in write
mode and gives its functional argument the corresponding file descriptor).
NOTE: you should never let the resource escape the
scope of the `with_resource` call, because it will not be valid outside.
OCaml's type system doesn't make it easy to forbid that so we rely
on convention here (it would be possible, but cumbersome, using
a record with an explicitly quantified function type).
Now we can read the file again:
```ocaml
# let lines : string list = CCIO.with_in "./foobar" CCIO.read_lines_l ;;
val lines : string list = ["hello"; "world"]
```
There are some other functions in `CCIO` that return _generators_
instead of lists. The type of generators in containers
is `type 'a gen = unit -> 'a option` (combinators can be
found in the opam library called "gen"). A generator is to be called
to obtain successive values, until it returns `None` (which means it
has been exhausted). In particular, python users might recognize
the function
```ocaml non-deterministic=command
# CCIO.File.walk ;;
- : string -> walk_item gen = <fun>;;
```
where `type walk_item = [ ``Dir | ``File ] * string` is a path
paired with a flag distinguishing files from directories.
### To go further: `containers-data`
There is also a library called `containers-data`, with lots of
more specialized data-structures.
The documentation contains the API for all the modules; they also provide
interface to `iter` and, as the rest of containers, minimize
dependencies over other modules. To use `containers-data` you need to link it,
either in your build system or by `#require containers-data;;`
A quick example based on purely functional double-ended queues:
```ocaml
# #require "containers-data";;
# #install_printer CCFQueue.pp (* better printing of queues! *);;
# let q = CCFQueue.of_list [2;3;4] ;;
val q : int CCFQueue.t = queue {2; 3; 4}
# let q2 = q |> CCFQueue.cons 1 |> CCFQueue.cons 0 ;;
val q2 : int CCFQueue.t = queue {0; 1; 2; 3; 4}
# (* remove first element *)
CCFQueue.take_front q2;;
- : (int * int CCFQueue.t) option = Some (0, queue {1; 2; 3; 4})
# (* q was not changed *)
CCFQueue.take_front q;;
- : (int * int CCFQueue.t) option = Some (2, queue {3; 4})
# (* take works on both ends of the queue *)
CCFQueue.take_back_l 2 q2;;
- : int CCFQueue.t * int list = (queue {0; 1; 2}, [3; 4])
```
### Common Type Definitions
Some structural types are used throughout the library:
- `gen`: `'a gen = unit -> 'a option` is an iterator type. Many combinators
are defined in the opam library [gen](https://github.com/c-cube/gen)
- `iter`: `'a iter = (unit -> 'a) -> unit` is also an iterator type, formerly
named `sequence`.
It is easier to define on data structures than `gen`, but it a bit less
powerful. The opam library [iter](https://github.com/c-cube/iter)
can be used to consume and produce values of this type.
It was renamed
from `'a sequence` to `'a iter` to distinguish it better from `Core.Sequence`
and the standard `seq`.
- `error`: `'a or_error = ('a, string) result = Error of string | Ok of 'a`
using the standard `result` type, supported in `CCResult`.
- `printer`: `'a printer = Format.formatter -> 'a -> unit` is a pretty-printer
to be used with the standard module `Format`. In particular, in many cases,
`"foo: %a" Foo.print foo` will type-check.
### Extended Documentation
See [the extended documentation](doc/containers.md) for more examples.
</details>
## HOWTO (for contributors)
<details>
### Make a release
Beforehand, check `grep deprecated -r src` to see whether some functions
can be removed.
- `make all`
- update version in `containers.opam`
- `make update_next_tag` (to update `@since` comments; be careful not to change symlinks)
- check status of modules (`{b status: foo}`) and update if required;
removed deprecated functions, etc.
- update `CHANGELOG.md` (see its end to find the right git command)
- commit the changes
- `make test doc`
- `export VERSION=<tag here>; git tag -f $VERSION; git push origin :$VERSION; git push origin $VERSION`
- new opam package: `opam publish https://github.com/c-cube/ocaml-containers/archive/<tag>.tar.gz`
- re-generate doc: `make doc` and put it into `gh-pages`
### List Authors
```
git log --format='%aN' | sort -u
```
</details>

161
_oasis Normal file
View file

@ -0,0 +1,161 @@
OASISFormat: 0.4
Name: containers
Version: 1.5
Homepage: https://github.com/c-cube/ocaml-containers
Authors: Simon Cruanes
License: BSD-2-clause
LicenseFile: LICENSE
Plugins: META (0.3), DevFiles (0.3)
OCamlVersion: >= 4.00.1
BuildTools: ocamlbuild
AlphaFeatures: ocamlbuild_more_args
# cygwin fails with anything else
XOCamlbuildExtraArgs: "-j 1"
Synopsis: A modular standard library focused on data structures.
Description:
Containers is a standard library (BSD license) focused on data structures,
combinators and iterators, without dependencies on unix. Every module is
independent and is prefixed with 'CC' in the global namespace. Some modules
extend the stdlib (e.g. CCList provides safe map/fold_right/append, and
additional functions on lists).
It also features optional libraries for dealing with strings, and
helpers for unix and threads.
Flag "unix"
Description: Build the containers.unix library (depends on Unix)
Default: true
Flag "thread"
Description: Build modules that depend on threads
Default: true
Flag "bench"
Description: Build and run benchmarks
Default: true
Library "containers"
Path: src/core
Modules: CCVector, CCHeap, CCList, CCOpt, CCPair,
CCFun, CCHash, CCInt, CCBool, CCFloat, CCArray, CCRef, CCSet,
CCOrd, CCRandom, CCString, CCHashtbl, CCMap, CCFormat, CCIO,
CCInt64, CCChar, CCResult, CCParse, CCArray_slice,
CCListLabels, CCArrayLabels, CCEqual,
Containers
BuildDepends: bytes, result
# BuildDepends: bytes, bisect_ppx
Library "containers_unix"
Path: src/unix
Modules: CCUnix
BuildDepends: bytes, result, unix
FindlibParent: containers
FindlibName: unix
Library "containers_sexp"
Path: src/sexp
Modules: CCSexp, CCSexp_lex
BuildDepends: bytes, result
FindlibParent: containers
FindlibName: sexp
Library "containers_data"
Path: src/data
Modules: CCMultiMap, CCMultiSet, CCTrie, CCFlatHashtbl, CCCache,
CCPersistentHashtbl, CCDeque, CCFQueue, CCBV, CCMixtbl,
CCMixmap, CCRingBuffer, CCIntMap, CCPersistentArray,
CCMixset, CCGraph, CCHashSet, CCBitField,
CCHashTrie, CCWBTree, CCRAL, CCSimple_queue,
CCImmutArray, CCHet, CCZipper
BuildDepends: bytes
# BuildDepends: bytes, bisect_ppx
FindlibParent: containers
FindlibName: data
Library "containers_iter"
Path: src/iter
Modules: CCKTree, CCKList, CCLazy_list
FindlibParent: containers
FindlibName: iter
Library "containers_thread"
Path: src/threads/
Modules: CCPool, CCLock, CCSemaphore, CCThread, CCBlockingQueue,
CCTimer
FindlibName: thread
FindlibParent: containers
Build$: flag(thread)
Install$: flag(thread)
BuildDepends: containers, threads
XMETARequires: containers, threads
Library "containers_top"
Path: src/top/
Modules: Containers_top
FindlibName: top
FindlibParent: containers
BuildDepends: compiler-libs.common, containers, containers.data,
containers.unix, containers.sexp, containers.iter
Document containers
Title: Containers docs
Type: ocamlbuild (0.3)
BuildTools+: ocamldoc
Build$: flag(docs) && flag(unix)
Install: true
XOCamlbuildPath: .
XOCamlbuildExtraArgs:
"-docflags '-colorize-code -short-functors -charset utf-8'"
XOCamlbuildLibraries:
containers, containers.iter, containers.data,
containers.thread, containers.unix, containers.sexp
Executable run_benchs
Path: benchs/
Install: false
CompiledObject: best
Build$: flag(bench)
MainIs: run_benchs.ml
BuildDepends: containers, qcheck,
containers.data, containers.iter, containers.thread,
sequence, gen, benchmark
Executable run_bench_hash
Path: benchs/
Install: false
CompiledObject: best
Build$: flag(bench)
MainIs: run_bench_hash.ml
BuildDepends: containers
PreBuildCommand: make qtest-gen
Executable run_qtest
Path: qtest/
Install: false
CompiledObject: best
MainIs: run_qtest.ml
Build$: flag(tests) && flag(unix)
BuildDepends: containers, containers.iter,
containers.sexp, containers.unix, containers.thread,
containers.data,
sequence, gen, unix, oUnit, qcheck
Test all
Command: ./run_qtest.native
TestTools: run_qtest
Run$: flag(tests) && flag(unix)
Executable id_sexp
Path: examples/
Install: false
CompiledObject: best
MainIs: id_sexp.ml
BuildDepends: containers.sexp
SourceRepository head
Type: git
Location: https://github.com/c-cube/ocaml-containers
Browser: https://github.com/c-cube/ocaml-containers/tree/master/src

123
_tags Normal file
View file

@ -0,0 +1,123 @@
# OASIS_START
# DO NOT EDIT (digest: 8cbdae3079e6ebc5257343569c6e2780)
# Ignore VCS directories, you can use the same kind of rule outside
# OASIS_START/STOP if you want to exclude directories that contains
# useless stuff for the build process
true: annot, bin_annot
<**/.svn>: -traverse
<**/.svn>: not_hygienic
".bzr": -traverse
".bzr": not_hygienic
".hg": -traverse
".hg": not_hygienic
".git": -traverse
".git": not_hygienic
"_darcs": -traverse
"_darcs": not_hygienic
# Library containers
"src/core/containers.cmxs": use_containers
<src/core/*.ml{,i,y}>: package(bytes)
<src/core/*.ml{,i,y}>: package(result)
# Library containers_unix
"src/unix/containers_unix.cmxs": use_containers_unix
<src/unix/*.ml{,i,y}>: package(bytes)
<src/unix/*.ml{,i,y}>: package(result)
<src/unix/*.ml{,i,y}>: package(unix)
# Library containers_sexp
"src/sexp/containers_sexp.cmxs": use_containers_sexp
<src/sexp/*.ml{,i,y}>: package(bytes)
<src/sexp/*.ml{,i,y}>: package(result)
# Library containers_data
"src/data/containers_data.cmxs": use_containers_data
<src/data/*.ml{,i,y}>: package(bytes)
# Library containers_iter
"src/iter/containers_iter.cmxs": use_containers_iter
# Library containers_thread
"src/threads/containers_thread.cmxs": use_containers_thread
<src/threads/*.ml{,i,y}>: package(bytes)
<src/threads/*.ml{,i,y}>: package(result)
<src/threads/*.ml{,i,y}>: package(threads)
<src/threads/*.ml{,i,y}>: use_containers
# Library containers_top
"src/top/containers_top.cmxs": use_containers_top
<src/top/*.ml{,i,y}>: package(bytes)
<src/top/*.ml{,i,y}>: package(compiler-libs.common)
<src/top/*.ml{,i,y}>: package(result)
<src/top/*.ml{,i,y}>: package(unix)
<src/top/*.ml{,i,y}>: use_containers
<src/top/*.ml{,i,y}>: use_containers_data
<src/top/*.ml{,i,y}>: use_containers_iter
<src/top/*.ml{,i,y}>: use_containers_sexp
<src/top/*.ml{,i,y}>: use_containers_unix
# Executable run_benchs
<benchs/run_benchs.{native,byte}>: package(benchmark)
<benchs/run_benchs.{native,byte}>: package(bytes)
<benchs/run_benchs.{native,byte}>: package(gen)
<benchs/run_benchs.{native,byte}>: package(qcheck)
<benchs/run_benchs.{native,byte}>: package(result)
<benchs/run_benchs.{native,byte}>: package(sequence)
<benchs/run_benchs.{native,byte}>: package(threads)
<benchs/run_benchs.{native,byte}>: use_containers
<benchs/run_benchs.{native,byte}>: use_containers_data
<benchs/run_benchs.{native,byte}>: use_containers_iter
<benchs/run_benchs.{native,byte}>: use_containers_thread
<benchs/*.ml{,i,y}>: package(benchmark)
<benchs/*.ml{,i,y}>: package(gen)
<benchs/*.ml{,i,y}>: package(qcheck)
<benchs/*.ml{,i,y}>: package(sequence)
<benchs/*.ml{,i,y}>: package(threads)
<benchs/*.ml{,i,y}>: use_containers_data
<benchs/*.ml{,i,y}>: use_containers_iter
<benchs/*.ml{,i,y}>: use_containers_thread
# Executable run_bench_hash
<benchs/run_bench_hash.{native,byte}>: package(bytes)
<benchs/run_bench_hash.{native,byte}>: package(result)
<benchs/run_bench_hash.{native,byte}>: use_containers
<benchs/*.ml{,i,y}>: package(bytes)
<benchs/*.ml{,i,y}>: package(result)
<benchs/*.ml{,i,y}>: use_containers
# Executable run_qtest
<qtest/run_qtest.{native,byte}>: package(bytes)
<qtest/run_qtest.{native,byte}>: package(gen)
<qtest/run_qtest.{native,byte}>: package(oUnit)
<qtest/run_qtest.{native,byte}>: package(qcheck)
<qtest/run_qtest.{native,byte}>: package(result)
<qtest/run_qtest.{native,byte}>: package(sequence)
<qtest/run_qtest.{native,byte}>: package(threads)
<qtest/run_qtest.{native,byte}>: package(unix)
<qtest/run_qtest.{native,byte}>: use_containers
<qtest/run_qtest.{native,byte}>: use_containers_data
<qtest/run_qtest.{native,byte}>: use_containers_iter
<qtest/run_qtest.{native,byte}>: use_containers_sexp
<qtest/run_qtest.{native,byte}>: use_containers_thread
<qtest/run_qtest.{native,byte}>: use_containers_unix
<qtest/*.ml{,i,y}>: package(bytes)
<qtest/*.ml{,i,y}>: package(gen)
<qtest/*.ml{,i,y}>: package(oUnit)
<qtest/*.ml{,i,y}>: package(qcheck)
<qtest/*.ml{,i,y}>: package(result)
<qtest/*.ml{,i,y}>: package(sequence)
<qtest/*.ml{,i,y}>: package(threads)
<qtest/*.ml{,i,y}>: package(unix)
<qtest/*.ml{,i,y}>: use_containers
<qtest/*.ml{,i,y}>: use_containers_data
<qtest/*.ml{,i,y}>: use_containers_iter
<qtest/*.ml{,i,y}>: use_containers_sexp
<qtest/*.ml{,i,y}>: use_containers_thread
<qtest/*.ml{,i,y}>: use_containers_unix
# Executable id_sexp
<examples/id_sexp.{native,byte}>: package(bytes)
<examples/id_sexp.{native,byte}>: package(result)
<examples/id_sexp.{native,byte}>: use_containers_sexp
<examples/*.ml{,i,y}>: package(bytes)
<examples/*.ml{,i,y}>: package(result)
<examples/*.ml{,i,y}>: use_containers_sexp
# OASIS_STOP
<tests/*.ml{,i}>: thread
<src/threads/*.ml{,i}>: thread
<src/core/CCVector.cmx> or <src/core/CCString.cmx>: inline(25)
<src/data/CCFlatHashtbl.cm*> or <src/data/CCHashTrie.cm*> or <src/data/CCPersistent*>: inline(15)
<src/core/CCMap.*> or <src/core/CCSet.*> or <src/core/CCList.*>: warn(-32)
<src/**/*.ml> and not <src/misc/*.ml>: warn(+a-4-44-58-60@8)
true: no_alias_deps, safe_string, short_paths, color(always)
<src/**/*Labels.cm*>: nolabels

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: 0f1ca0e2b031ae1710e26abf02cca256)
Containers_advanced
# OASIS_STOP

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: 0f1ca0e2b031ae1710e26abf02cca256)
Containers_advanced
# OASIS_STOP

View file

@ -0,0 +1,7 @@
# OASIS_START
# DO NOT EDIT (digest: 5a399cd532edb84596f3034081578694)
CCLinq
CCBatch
CCCat
CCMonadIO
# OASIS_STOP

View file

@ -1,24 +0,0 @@
(executables
(names run_benchs run_bench_hash run_objsize)
(libraries
containers
containers_pvec
containers-data
benchmark
gen
iter
qcheck
oseq
batteries
base
sek)
(flags :standard -warn-error -3-5 -w -60 -safe-string -color always)
(optional)
(ocamlopt_flags
:standard
-O3
-color
always
-unbox-closures
-unbox-closures-factor
20))

View file

@ -22,52 +22,50 @@ open Obj
(*s Pointers already visited are stored in a hash-table, where (*s Pointers already visited are stored in a hash-table, where
comparisons are done using physical equality. *) comparisons are done using physical equality. *)
module H = Hashtbl.Make (struct module H = Hashtbl.Make(
type t = Obj.t struct
type t = Obj.t
let equal = ( == ) let equal = (==)
let hash o = Hashtbl.hash (magic o : int) let hash o = Hashtbl.hash (magic o : int)
end) end)
let node_table = (H.create 257 : unit H.t) let node_table = (H.create 257 : unit H.t)
let in_table o = let in_table o = try H.find node_table o; true with Not_found -> false
try
H.find node_table o;
true
with Not_found -> false
let add_in_table o = H.add node_table o () let add_in_table o = H.add node_table o ()
let reset_table () = H.clear node_table let reset_table () = H.clear node_table
(*s Objects are traversed recursively, as soon as their tags are less than (*s Objects are traversed recursively, as soon as their tags are less than
[no_scan_tag]. [count] records the numbers of words already visited. *) [no_scan_tag]. [count] records the numbers of words already visited. *)
let size_of_double = size (repr 1.0) let size_of_double = size (repr 1.0)
let count = ref 0 let count = ref 0
let rec traverse t = let rec traverse t =
if not (in_table t) then ( if not (in_table t) then begin
add_in_table t; add_in_table t;
if is_block t then ( if is_block t then begin
let n = size t in let n = size t in
let tag = tag t in let tag = tag t in
if tag < no_scan_tag then ( if tag < no_scan_tag then begin
count := !count + 1 + n; count := !count + 1 + n;
for i = 0 to n - 1 do for i = 0 to n - 1 do
let f = field t i in let f = field t i in
if is_block f then traverse f if is_block f then traverse f
done done
) else if tag = string_tag then end else if tag = string_tag then
count := !count + 1 + n count := !count + 1 + n
else if tag = double_tag then else if tag = double_tag then
count := !count + size_of_double count := !count + size_of_double
else if tag = double_array_tag then else if tag = double_array_tag then
count := !count + 1 + (size_of_double * n) count := !count + 1 + size_of_double * n
else else
incr count incr count
) end
) end
(*s Sizes of objects in words and in bytes. The size in bytes is computed (*s Sizes of objects in words and in bytes. The size in bytes is computed
system-independently according to [Sys.word_size]. *) system-independently according to [Sys.word_size]. *)
@ -78,5 +76,8 @@ let size_w o =
traverse (repr o); traverse (repr o);
!count !count
let size_b o = size_w o * (Sys.word_size / 8) let size_b o = (size_w o) * (Sys.word_size / 8)
let size_kb o = size_w o / (8192 / Sys.word_size)
let size_kb o = (size_w o) / (8192 / Sys.word_size)

View file

@ -1,55 +1,58 @@
(* reference implementations for some structures, for comparison purpose *) (* reference implementations for some structures, for comparison purpose *)
module PersistentHashtbl (H : Hashtbl.HashedType) = struct module PersistentHashtbl(H : Hashtbl.HashedType) = struct
module Table = Hashtbl.Make (H) module Table = Hashtbl.Make(H)
(** Imperative hashtable *) (** Imperative hashtable *)
type key = H.t type key = H.t
type 'a t = 'a zipper ref type 'a t = 'a zipper ref
and 'a zipper = and 'a zipper =
| Table of 'a Table.t (** Concrete table *) | Table of 'a Table.t (** Concrete table *)
| Add of key * 'a * 'a t (** Add key *) | Add of key * 'a * 'a t (** Add key *)
| Replace of key * 'a * 'a t (** Replace key by value *) | Replace of key * 'a * 'a t (** Replace key by value *)
| Remove of key * 'a t (** As the table, but without given key *) | Remove of key * 'a t (** As the table, but without given key *)
let create i =
ref (Table (Table.create i))
let create i = ref (Table (Table.create i))
let empty () = create 11 let empty () = create 11
(* pass continuation to get a tailrec rerooting *) (* pass continuation to get a tailrec rerooting *)
let rec _reroot t k = let rec _reroot t k = match !t with
match !t with | Table tbl -> k tbl (* done *)
| Table tbl -> k tbl (* done *) | Add (key, v, t') ->
| Add (key, v, t') -> _reroot t'
_reroot t' (fun tbl -> (fun tbl ->
t' := Remove (key, t); t' := Remove (key, t);
Table.add tbl key v; Table.add tbl key v;
t := Table tbl; t := Table tbl;
k tbl) k tbl)
| Replace (key, v, t') -> | Replace (key, v, t') ->
_reroot t' (fun tbl -> _reroot t'
let v' = Table.find tbl key in (fun tbl ->
t' := Replace (key, v', t); let v' = Table.find tbl key in
t := Table tbl; t' := Replace (key, v', t);
Table.replace tbl key v; t := Table tbl;
k tbl) Table.replace tbl key v;
| Remove (key, t') -> k tbl)
_reroot t' (fun tbl -> | Remove (key, t') ->
let v = Table.find tbl key in _reroot t'
t' := Add (key, v, t); (fun tbl ->
t := Table tbl; let v = Table.find tbl key in
Table.remove tbl key; t' := Add (key, v, t);
k tbl) t := Table tbl;
Table.remove tbl key;
k tbl)
(* Reroot: modify the zipper so that the current node is a proper (* Reroot: modify the zipper so that the current node is a proper
hashtable, and return the hashtable *) hashtable, and return the hashtable *)
let reroot t = let reroot t = match !t with
match !t with
| Table tbl -> tbl | Table tbl -> tbl
| _ -> _reroot t (fun x -> x) | _ -> _reroot t (fun x -> x)
let is_empty t = Table.length (reroot t) = 0 let is_empty t = Table.length (reroot t) = 0
let find t k = Table.find (reroot t) k let find t k = Table.find (reroot t) k
(*$R (*$R
@ -88,8 +91,13 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
*) *)
let get_exn k t = find t k let get_exn k t = find t k
let get k t = try Some (find t k) with Not_found -> None
let get k t =
try Some (find t k)
with Not_found -> None
let mem t k = Table.mem (reroot t) k let mem t k = Table.mem (reroot t) k
let length t = Table.length (reroot t) let length t = Table.length (reroot t)
(*$R (*$R
@ -112,9 +120,11 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let t' = ref (Table tbl) in let t' = ref (Table tbl) in
(* update [t] to point to the new hashtable *) (* update [t] to point to the new hashtable *)
(try (try
let v' = Table.find tbl k in let v' = Table.find tbl k in
t := Replace (k, v', t') t := Replace (k, v', t')
with Not_found -> t := Remove (k, t')); with Not_found ->
t := Remove (k, t')
);
(* modify the underlying hashtable *) (* modify the underlying hashtable *)
Table.replace tbl k v; Table.replace tbl k v;
t' t'
@ -160,12 +170,12 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let h = H.of_list l in let h = H.of_list l in
let h = List.fold_left (fun h (k,_) -> H.remove h k) h l in let h = List.fold_left (fun h (k,_) -> H.remove h k) h l in
H.is_empty h) H.is_empty h)
*) *)
let update t k f = let update t k f =
let v = get k t in let v = get k t in
match v, f v with match v, f v with
| None, None -> t (* no change *) | None, None -> t (* no change *)
| Some _, None -> remove t k | Some _, None -> remove t k
| _, Some v' -> replace t k v' | _, Some v' -> replace t k v'
@ -199,11 +209,10 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let tbl = reroot t in let tbl = reroot t in
let res = Table.create (Table.length tbl) in let res = Table.create (Table.length tbl) in
Table.iter Table.iter
(fun k v -> (fun k v -> match f k v with
match f k v with
| None -> () | None -> ()
| Some v' -> Table.replace res k v') | Some v' -> Table.replace res k v'
tbl; ) tbl;
ref (Table res) ref (Table res)
exception ExitPTbl exception ExitPTbl
@ -222,17 +231,17 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let merge f t1 t2 = let merge f t1 t2 =
let tbl = Table.create (max (length t1) (length t2)) in let tbl = Table.create (max (length t1) (length t2)) in
iter t1 (fun k v1 -> iter t1
(fun k v1 ->
let v2 = try Some (find t2 k) with Not_found -> None in let v2 = try Some (find t2 k) with Not_found -> None in
match f k (Some v1) v2 with match f k (Some v1) v2 with
| None -> () | None -> ()
| Some v' -> Table.replace tbl k v'); | Some v' -> Table.replace tbl k v');
iter t2 (fun k v2 -> iter t2
if not (mem t1 k) then ( (fun k v2 ->
match f k None (Some v2) with if not (mem t1 k) then match f k None (Some v2) with
| None -> () | None -> ()
| Some _ -> Table.replace tbl k v2 | Some _ -> Table.replace tbl k v2);
));
ref (Table tbl) ref (Table tbl)
(*$R (*$R
@ -253,11 +262,13 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let add_seq init seq = let add_seq init seq =
let tbl = ref init in let tbl = ref init in
seq (fun (k, v) -> tbl := replace !tbl k v); seq (fun (k,v) -> tbl := replace !tbl k v);
!tbl !tbl
let of_seq seq = add_seq (empty ()) seq let of_seq seq = add_seq (empty ()) seq
let add_list init l = add_seq init (fun k -> List.iter k l)
let add_list init l =
add_seq init (fun k -> List.iter k l)
(*$QR (*$QR
_list_int_int (fun l -> _list_int_int (fun l ->
@ -282,7 +293,7 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let to_list t = let to_list t =
let tbl = reroot t in let tbl = reroot t in
let bindings = Table.fold (fun k v acc -> (k, v) :: acc) tbl [] in let bindings = Table.fold (fun k v acc -> (k,v)::acc) tbl [] in
bindings bindings
(*$R (*$R
@ -291,9 +302,10 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
OUnit.assert_equal my_list (List.sort compare l) OUnit.assert_equal my_list (List.sort compare l)
*) *)
let to_seq t k = let to_seq t =
let tbl = reroot t in fun k ->
Table.iter (fun x y -> k (x, y)) tbl let tbl = reroot t in
Table.iter (fun x y -> k (x,y)) tbl
(*$R (*$R
let h = H.of_seq my_seq in let h = H.of_seq my_seq in
@ -304,34 +316,31 @@ module PersistentHashtbl (H : Hashtbl.HashedType) = struct
let equal eq t1 t2 = let equal eq t1 t2 =
length t1 = length t2 length t1 = length t2
&& for_all &&
(fun k v -> for_all
match get k t2 with (fun k v -> match get k t2 with
| None -> false | None -> false
| Some v' -> eq v v') | Some v' -> eq v v'
t1 ) t1
let pp pp_k pp_v buf t = let pp pp_k pp_v buf t =
Buffer.add_string buf "{"; Buffer.add_string buf "{";
let first = ref true in let first = ref true in
iter t (fun k v -> iter t
if !first then (fun k v ->
first := false if !first then first:=false else Buffer.add_string buf ", ";
else Printf.bprintf buf "%a -> %a" pp_k k pp_v v
Buffer.add_string buf ", "; );
Printf.bprintf buf "%a -> %a" pp_k k pp_v v);
Buffer.add_string buf "}" Buffer.add_string buf "}"
let print pp_k pp_v fmt t = let print pp_k pp_v fmt t =
Format.pp_print_string fmt "{"; Format.pp_print_string fmt "{";
let first = ref true in let first = ref true in
iter t (fun k v -> iter t
if !first then (fun k v ->
first := false if !first then first:=false
else ( else (Format.pp_print_string fmt ", "; Format.pp_print_cut fmt ());
Format.pp_print_string fmt ", "; Format.fprintf fmt "%a -> %a" pp_k k pp_v v
Format.pp_print_cut fmt () );
);
Format.fprintf fmt "%a -> %a" pp_k k pp_v v);
Format.pp_print_string fmt "}" Format.pp_print_string fmt "}"
end end

View file

@ -4,39 +4,46 @@ type tree =
| Empty | Empty
| Node of int * tree list | Node of int * tree list
let mk_node i l = Node (i, l) let mk_node i l = Node (i,l)
let random_tree = let random_tree =
CCRandom.(fix
~base:(return Empty)
~subn:[int 10, (fun sublist -> pure mk_node <*> small_int <*> sublist)]
(int_range 15 150)
)
let random_list =
CCRandom.( CCRandom.(
fix ~base:(return Empty) int 5 >>= fun len ->
~subn:[ (int 10, fun sublist -> pure mk_node <*> small_int <*> sublist) ] CCList.random_len len random_tree
(int_range 15 150)) )
let rec eq t1 t2 = let rec eq t1 t2 = match t1, t2 with
match t1, t2 with
| Empty, Empty -> true | Empty, Empty -> true
| Node (i1, l1), Node (i2, l2) -> i1 = i2 && CCList.equal eq l1 l2 | Node(i1,l1), Node (i2,l2) -> i1=i2 && CCList.equal eq l1 l2
| Node _, _ | _, Node _ -> false | Node _, _
| _, Node _ -> false
let rec hash_tree t = let rec hash_tree t = match t with
match t with
| Empty -> CCHash.string "empty" | Empty -> CCHash.string "empty"
| Node (i, l) -> CCHash.(combine2 (int i) (list hash_tree l)) | Node (i, l) ->
CCHash.(combine2 (int i) (list hash_tree l))
module H = Hashtbl.Make (struct module H = Hashtbl.Make(struct
type t = tree type t = tree
let equal = eq let equal = eq
let hash = hash_tree let hash = hash_tree
end) end)
let print_hashcons_stats st = let print_hashcons_stats st =
let open Hashtbl in let open Hashtbl in
Format.printf "tbl stats: %d elements, num buckets: %d, max bucket: %d@." Format.printf
st.num_bindings st.num_buckets st.max_bucket_length; "tbl stats: %d elements, num buckets: %d, max bucket: %d@."
Array.iteri st.num_bindings st.num_buckets st.max_bucket_length;
(fun i n -> Format.printf " %d\t buckets have length %d@." n i) Array.iteri
st.bucket_histogram (fun i n -> Format.printf " %d\t buckets have length %d@." n i)
st.bucket_histogram
let () = let () =
let st = Random.State.make_self_init () in let st = Random.State.make_self_init () in
@ -54,3 +61,4 @@ let () =
List.iter (fun t -> Hashtbl.replace tbl' t ()) l; List.iter (fun t -> Hashtbl.replace tbl' t ()) l;
print_hashcons_stats (Hashtbl.stats tbl'); print_hashcons_stats (Hashtbl.stats tbl');
() ()

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
#!/bin/sh
exec dune exec --profile=release benchs/run_benchs.exe -- $@

View file

@ -1,94 +0,0 @@
(* module Deque = Core_kernel.Deque *)
module Int_map = CCMap.Make (CCInt)
module Int_set = CCSet.Make (CCInt)
let dup = CCPair.dup
let id = CCFun.id
let ns n = List.init n CCFun.id
let iter_range n f = List.iter f (ns n)
let gen_cons x xs =
let saw_x = ref false in
fun () ->
if !saw_x then (
saw_x := true;
Some x
) else
xs ()
let front = Sek.front
let dummy = 0
let types =
[
("Stdlib.List", fun n -> Obj.magic @@ ns n);
("Stdlib.Array", fun n -> Obj.magic @@ Array.init n id);
( "Stdlib.Hashtbl",
fun n -> Obj.magic @@ CCHashtbl.of_iter Iter.(init dup |> take n) );
( "Base.Hashtbl",
fun n -> Obj.magic @@ Base.Hashtbl.Poly.of_alist_exn (List.init n dup) );
( "Stdlib.Map",
fun n -> Obj.magic @@ Int_map.of_iter Iter.(init dup |> take n) );
( "Stdlib.Set",
fun n -> Obj.magic @@ Int_set.of_iter Iter.(init id |> take n) );
("CCFun_vec", fun n -> Obj.magic @@ CCFun_vec.of_list (ns n));
("CCRAL", fun n -> Obj.magic @@ CCRAL.of_list (ns n));
("BatVect", fun n -> Obj.magic @@ BatVect.of_list (ns n));
( "Sek.Persistent",
fun n ->
Obj.magic
@@ List.fold_left
(Sek.Persistent.push front)
(Sek.Persistent.create dummy)
(ns n) );
( "Sek.Ephemeral",
fun n ->
Obj.magic
@@
let c = Sek.Ephemeral.create dummy in
iter_range n (Sek.Ephemeral.push front c);
c );
( "CCVector",
fun n ->
Obj.magic
@@
let c = CCVector.create () in
iter_range n (CCVector.push c);
c );
(* "Core_kernel.Deque", (fun n -> Obj.magic @@ let c = Deque.create () in iter_range n (Deque.enqueue_back c); c); *)
( "Base.Queue",
fun n ->
Obj.magic
@@
let c = Base.Queue.create () in
iter_range n (Base.Queue.enqueue c);
c );
( "Stdlib.Queue",
fun n ->
Obj.magic
@@
let q = Queue.create () in
iter_range n (fun x -> Queue.push x q);
q );
("CCQueue", fun n -> Obj.magic @@ CCDeque.of_list (ns n));
("Iter", fun n -> Obj.magic @@ List.fold_right Iter.cons (ns n) Iter.empty);
("Gen", fun n -> Obj.magic @@ List.fold_right gen_cons (ns n) Gen.empty);
( "Stdlib.Seq",
fun n -> Obj.magic @@ List.fold_right OSeq.cons (ns n) OSeq.empty );
]
let () =
let sizes = [ 0; 1; 10; 100; 1000; 10000 ] in
Printf.printf "%-20s " "";
sizes |> List.iter (fun n -> Printf.printf "%6d " n);
Printf.printf "\n";
types
|> List.iter (fun (name, create) ->
Printf.printf "%-20s: " name;
sizes
|> List.iter (fun n ->
let obj = create n in
let size = Objsize.size_w obj in
(* let size = Obj.reachable_words (Obj.repr obj) in *)
Printf.printf "%6d " size);
Printf.printf "\n")

27
configure vendored Executable file
View file

@ -0,0 +1,27 @@
#!/bin/sh
# OASIS_START
# DO NOT EDIT (digest: dc86c2ad450f91ca10c931b6045d0499)
set -e
FST=true
for i in "$@"; do
if $FST; then
set --
FST=false
fi
case $i in
--*=*)
ARG=${i%%=*}
VAL=${i##*=}
set -- "$@" "$ARG" "$VAL"
;;
*)
set -- "$@" "$i"
;;
esac
done
ocaml setup.ml -configure "$@"
# OASIS_STOP

View file

@ -1,26 +0,0 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
version: "3.17"
synopsis: "A set of advanced datatypes for containers"
maintainer: ["c-cube"]
authors: ["c-cube"]
license: "BSD-2-Clause"
tags: ["containers" "RAL" "function" "vector" "okasaki"]
homepage: "https://github.com/c-cube/ocaml-containers/"
bug-reports: "https://github.com/c-cube/ocaml-containers/issues"
depends: [
"dune" {>= "3.0"}
"ocaml" {>= "4.08"}
"containers" {= version}
"qcheck-core" {>= "0.18" & with-test}
"iter" {with-test}
"gen" {with-test}
"mdx" {with-test}
"odoc" {with-doc}
]
dev-repo: "git+https://github.com/c-cube/ocaml-containers.git"
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name ] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & arch != "x86_32" & arch != "arm32"}
]

View file

@ -1,5 +0,0 @@
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name ] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & arch != "x86_32" & arch != "arm32"}
]

69
containers.odocl Normal file
View file

@ -0,0 +1,69 @@
# OASIS_START
# DO NOT EDIT (digest: fe2373b07664be05f7322781403afad6)
src/core/CCVector
src/core/CCHeap
src/core/CCList
src/core/CCOpt
src/core/CCPair
src/core/CCFun
src/core/CCHash
src/core/CCInt
src/core/CCBool
src/core/CCFloat
src/core/CCArray
src/core/CCRef
src/core/CCSet
src/core/CCOrd
src/core/CCRandom
src/core/CCString
src/core/CCHashtbl
src/core/CCMap
src/core/CCFormat
src/core/CCIO
src/core/CCInt64
src/core/CCChar
src/core/CCResult
src/core/CCParse
src/core/CCArray_slice
src/core/CCListLabels
src/core/CCArrayLabels
src/core/CCEqual
src/core/Containers
src/iter/CCKTree
src/iter/CCKList
src/iter/CCLazy_list
src/data/CCMultiMap
src/data/CCMultiSet
src/data/CCTrie
src/data/CCFlatHashtbl
src/data/CCCache
src/data/CCPersistentHashtbl
src/data/CCDeque
src/data/CCFQueue
src/data/CCBV
src/data/CCMixtbl
src/data/CCMixmap
src/data/CCRingBuffer
src/data/CCIntMap
src/data/CCPersistentArray
src/data/CCMixset
src/data/CCGraph
src/data/CCHashSet
src/data/CCBitField
src/data/CCHashTrie
src/data/CCWBTree
src/data/CCRAL
src/data/CCSimple_queue
src/data/CCImmutArray
src/data/CCHet
src/data/CCZipper
src/threads/CCPool
src/threads/CCLock
src/threads/CCSemaphore
src/threads/CCThread
src/threads/CCBlockingQueue
src/threads/CCTimer
src/unix/CCUnix
src/sexp/CCSexp
src/sexp/CCSexp_lex
# OASIS_STOP

View file

@ -1,31 +0,0 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
version: "3.17"
synopsis:
"A modular, clean and powerful extension of the OCaml standard library"
maintainer: ["c-cube"]
authors: ["c-cube"]
license: "BSD-2-Clause"
tags: ["stdlib" "containers" "iterators" "list" "heap" "queue"]
homepage: "https://github.com/c-cube/ocaml-containers/"
bug-reports: "https://github.com/c-cube/ocaml-containers/issues"
depends: [
"dune" {>= "3.0"}
"ocaml" {>= "4.08"}
"either"
"dune-configurator"
"qcheck-core" {>= "0.18" & with-test}
"yojson" {with-test}
"iter" {with-test}
"gen" {with-test}
"csexp" {with-test}
"uutf" {with-test}
"odoc" {with-doc}
]
depopts: ["base-unix" "base-threads"]
dev-repo: "git+https://github.com/c-cube/ocaml-containers.git"
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name ] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & arch != "x86_32" & arch != "arm32"}
]

View file

@ -1,5 +0,0 @@
build: [
["dune" "build" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name ] {with-doc}
["dune" "runtest" "-p" name "-j" jobs] {with-test & arch != "x86_32" & arch != "arm32"}
]

View file

@ -0,0 +1,7 @@
# OASIS_START
# DO NOT EDIT (digest: c39cb4de2de5f975c95c1e05f9661ea6)
advanced/CCLinq
advanced/CCBatch
advanced/CCCat
advanced/CCMonadIO
# OASIS_STOP

6
containers_lwt.odocl Normal file
View file

@ -0,0 +1,6 @@
# OASIS_START
# DO NOT EDIT (digest: e7bd30038718864173f30ddcb755f758)
lwt/Behavior
lwt/Lwt_automaton
lwt/Lwt_actor
# OASIS_STOP

34
containers_misc.odocl Normal file
View file

@ -0,0 +1,34 @@
# OASIS_START
# DO NOT EDIT (digest: df85a5182175d1029216007c66a27aa4)
misc/FHashtbl
misc/FlatHashtbl
misc/Hashset
misc/Heap
misc/LazyGraph
misc/PersistentGraph
misc/PHashtbl
misc/SkipList
misc/SplayTree
misc/SplayMap
misc/Univ
misc/Bij
misc/PiCalculus
misc/RAL
misc/UnionFind
misc/SmallSet
misc/AbsSet
misc/CSM
misc/TTree
misc/PrintBox
misc/HGraph
misc/Automaton
misc/Conv
misc/Bidir
misc/Iteratee
misc/BTree
misc/Ty
misc/Cause
misc/AVL
misc/ParseReact
misc/Mixtbl
# OASIS_STOP

5
containers_string.odocl Normal file
View file

@ -0,0 +1,5 @@
# OASIS_START
# DO NOT EDIT (digest: fae37ce560d3fa23ae92d95d4ecca210)
string/KMP
string/Levenshtein
# OASIS_STOP

87
core/META Normal file
View file

@ -0,0 +1,87 @@
# OASIS_START
# DO NOT EDIT (digest: 29eba35d8937ec2340c27a97da9180a6)
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "bytes"
archive(byte) = "containers.cma"
archive(byte, plugin) = "containers.cma"
archive(native) = "containers.cmxa"
archive(native, plugin) = "containers.cmxs"
exists_if = "containers.cma"
package "thread" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "containers threads"
archive(byte) = "containers_thread.cma"
archive(byte, plugin) = "containers_thread.cma"
archive(native) = "containers_thread.cmxa"
archive(native, plugin) = "containers_thread.cmxs"
exists_if = "containers_thread.cma"
)
package "string" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
archive(byte) = "containers_string.cma"
archive(byte, plugin) = "containers_string.cma"
archive(native) = "containers_string.cmxa"
archive(native, plugin) = "containers_string.cmxs"
exists_if = "containers_string.cma"
)
package "pervasives" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "containers"
archive(byte) = "containers_pervasives.cma"
archive(byte, plugin) = "containers_pervasives.cma"
archive(native) = "containers_pervasives.cmxa"
archive(native, plugin) = "containers_pervasives.cmxs"
exists_if = "containers_pervasives.cma"
)
package "misc" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "unix containers"
archive(byte) = "containers_misc.cma"
archive(byte, plugin) = "containers_misc.cma"
archive(native) = "containers_misc.cmxa"
archive(native, plugin) = "containers_misc.cmxs"
exists_if = "containers_misc.cma"
)
package "lwt" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "containers lwt lwt.unix containers.misc"
archive(byte) = "containers_lwt.cma"
archive(byte, plugin) = "containers_lwt.cma"
archive(native) = "containers_lwt.cmxa"
archive(native, plugin) = "containers_lwt.cmxs"
exists_if = "containers_lwt.cma"
)
package "cgi" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "containers CamlGI"
archive(byte) = "containers_cgi.cma"
archive(byte, plugin) = "containers_cgi.cma"
archive(native) = "containers_cgi.cmxa"
archive(native, plugin) = "containers_cgi.cmxs"
exists_if = "containers_cgi.cma"
)
package "advanced" (
version = "0.6.1"
description = "A modular standard library focused on data structures."
requires = "containers"
archive(byte) = "containers_advanced.cma"
archive(byte, plugin) = "containers_advanced.cma"
archive(native) = "containers_advanced.cmxa"
archive(native, plugin) = "containers_advanced.cmxs"
exists_if = "containers_advanced.cma"
)
# OASIS_STOP

37
core/containers.mldylib Normal file
View file

@ -0,0 +1,37 @@
# OASIS_START
# DO NOT EDIT (digest: 8d84707fdc7358bdadca9b7202118243)
CCVector
CCDeque
CCGen
Gen_intf
CCSequence
CCFQueue
CCMultiMap
CCMultiSet
CCBV
CCPrint
CCPersistentHashtbl
CCError
CCHeap
CCList
CCOpt
CCPair
CCFun
CCHash
CCKList
CCInt
CCBool
CCFloat
CCArray
CCOrd
CCIO
CCRandom
CCKTree
CCTrie
CCString
CCHashtbl
CCFlatHashtbl
CCSexp
CCMap
CCCache
# OASIS_STOP

37
core/containers.mllib Normal file
View file

@ -0,0 +1,37 @@
# OASIS_START
# DO NOT EDIT (digest: 8d84707fdc7358bdadca9b7202118243)
CCVector
CCDeque
CCGen
Gen_intf
CCSequence
CCFQueue
CCMultiMap
CCMultiSet
CCBV
CCPrint
CCPersistentHashtbl
CCError
CCHeap
CCList
CCOpt
CCPair
CCFun
CCHash
CCKList
CCInt
CCBool
CCFloat
CCArray
CCOrd
CCIO
CCRandom
CCKTree
CCTrie
CCString
CCHashtbl
CCFlatHashtbl
CCSexp
CCMap
CCCache
# OASIS_STOP

32
doc/build_deps.ml Executable file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env ocaml
(* note: this requires to generate documentation first, so that
.odoc files are generated *)
#use "topfind";;
#require "containers";;
#require "containers.io";;
#require "gen";;
#require "unix";;
let odoc_files =
CCIO.File.walk "_build"
|> Gen.filter_map
(function
| `File, f when CCString.suffix ~suf:".odoc" f -> Some f
| _ -> None
)
|> Gen.flat_map
(fun f -> Gen.of_list ["-load"; f])
|> Gen.to_list
;;
let out = "deps.dot";;
let cmd =
"ocamldoc -dot -o " ^ out ^ " " ^ String.concat " " odoc_files
;;
print_endline ("run: " ^ cmd);;
Unix.system cmd;;
print_endline ("output in " ^ out);;

View file

@ -1,12 +1,12 @@
# More about OCaml-containers = OCaml-containers =
:toc: macro
:source-highlighter: pygments
This document contains more information on some modules of Containers. This document contains more information on some modules of Containers.
```ocaml toc::[]
# #require "containers";;
```
## Hash combinators: `CCHash` == Hash combinators: `CCHash`
Although OCaml provides polymorphic hash tables (`('a,'b) Hashtbl.t`) Although OCaml provides polymorphic hash tables (`('a,'b) Hashtbl.t`)
using the polymorphic equality `(=)` and hash `Hashtbl.hash`, it is often using the polymorphic equality `(=)` and hash `Hashtbl.hash`, it is often
@ -15,67 +15,63 @@ with custom equality and hash functions.
`CCHash` provides combinators for writing hash functions: `CCHash` provides combinators for writing hash functions:
```ocaml [source,OCaml]
----
# module H = CCHash;; # module H = CCHash;;
module H = CCHash
# let hash1 : (int * bool) list H.t = H.(list (pair int bool));; # let hash1 : (int * bool) list H.t = H.(list (pair int bool));;
val hash1 : (int * bool) list H.t = <fun>
```
```ocaml non-deterministic=output
# hash1 [1, true; 2, false; 3, true];; # hash1 [1, true; 2, false; 3, true];;
- : int = 636041136 - : int = 636041136
(* the function hashes the whole value, can be costly *)
# hash1 CCList.(1 -- 1000 |> map (fun i->i, i mod 2 = 0));; # hash1 CCList.(1 -- 1000 |> map (fun i->i, i mod 2 = 0));;
- : int = 845685523 - : int = 845685523
# hash1 CCList.(1 -- 1001 |> map (fun i->i, i mod 2 = 0));; # hash1 CCList.(1 -- 1001 |> map (fun i->i, i mod 2 = 0));;
- : int = 381026697 - : int = 381026697
``` ----
The polymorphic hash function is still present, as `CCHash.poly`. The polymorphic hash function is still present, as `CCHash.poly`.
The functions `CCHash.list_comm` and `CCHash.array_comm` allow to hash The functions `CCHash.list_comm` and `CCHash.array_comm` allow to hash
lists and arrays while ignoring the order of elements: all permutations lists and arrays while ignoring the order of elements: all permutations
of the input will have the same hash. of the input will have the same hash.
## Parser Combinator: `CCParse`
== Parser Combinator: `CCParse`
:toc: macro
:source-highlighter: pygments
The module `CCParse` defines basic parser combinators on strings. The module `CCParse` defines basic parser combinators on strings.
Adapting [angstrom's tutorial example](https://github.com/inhabitedtype/angstrom#usage) Adapting https://github.com/inhabitedtype/angstrom#usage[angstrom's tutorial example] gives the following snippet.
gives the following snippet.
Note that backtracking is explicit in `CCParse`, hence Note that backtracking is explicit in `CCParse`, hence
the use of `try_` to allow it in some places. the use of `try_` to allow it in some places.
Explicit memoization with `memo` and `fix_memo` is also possible. Explicit memoization with `memo` and `fix_memo` is also possible.
```ocaml [source,OCaml]
open CCParse.Infix ----
module P = CCParse open CCParse.Infix;;
module P = CCParse;;
let parens p = P.try_ (P.char '(') *> p <* P.char ')' let parens p = P.try_ (P.char '(') *> p <* P.char ')' ;;
let add = P.char '+' *> P.return (+) let add = P.char '+' *> P.return (+) ;;
let sub = P.char '-' *> P.return (-) let sub = P.char '-' *> P.return (-) ;;
let mul = P.char '*' *> P.return ( * ) let mul = P.char '*' *> P.return ( * ) ;;
let div = P.char '/' *> P.return ( / ) let div = P.char '/' *> P.return ( / ) ;;
let integer = let integer =
P.chars1_if (function '0'..'9'->true|_->false) >|= int_of_string P.chars1_if (function '0'..'9'->true|_->false) >|= int_of_string ;;
let chainl1 e op = let chainl1 e op =
P.fix (fun r -> P.fix (fun r ->
e >>= fun x -> P.try_ (op <*> P.return x <*> r) <|> P.return x) e >>= fun x -> P.try_ (op <*> P.return x <*> r) <|> P.return x) ;;
let expr : int P.t = let expr : int P.t =
P.fix (fun expr -> P.fix (fun expr ->
let factor = parens expr <|> integer in let factor = parens expr <|> integer in
let term = chainl1 factor (mul <|> div) in let term = chainl1 factor (mul <|> div) in
chainl1 term (add <|> sub)) chainl1 term (add <|> sub)) ;;
```
Now we can parse strings using `expr`: P.parse_string expr "4*1+2";; (* Ok 6 *)
P.parse_string expr "4*(1+2)";; (* Ok 12 *)
```ocaml
# P.parse_string expr "4*1+2";; (* Ok 6 *)
- : int P.or_error = Result.Ok 6
# P.parse_string expr "4*(1+2)";; (* Ok 12 *)
- : int P.or_error = Result.Ok 12
```
----

166
doc/intro.txt Normal file
View file

@ -0,0 +1,166 @@
{1 Containers}
{2 Change Log}
See {{: https://github.com/c-cube/ocaml-containers/blob/master/CHANGELOG.adoc } this file}
{2 License}
This code is free, under the BSD license.
The logo (media/logo.png) is
CC-SA3 {{:http://en.wikipedia.org/wiki/File:Hypercube.svg} wikimedia}
{2 Contents}
The design is mostly centered around polymorphism rather than functors. Such
structures comprise (some modules in misc/, some other in core/):
the core library, containers, now depends on base-bytes (provided
by ocamlfind).
{4 Core Modules (extension of the standard library)}
{b findlib name}: containers
{!modules:
CCArray
CCArrayLabels
CCArray_slice
CCBool
CCChar
CCEqual
CCFloat
CCFormat
CCFun
CCHash
CCHashtbl
CCHeap
CCIO
CCInt
CCInt64
CCList
CCListLabels
CCMap
CCOpt
CCOrd
CCPair
CCParse
CCRandom
CCRef
CCResult
CCSet
CCString
CCVector
Containers
}
The module {!Containers} contains aliases to most other modules defined
in {i containers core}, and mixins
such as:
{[ module List = struct
include List
include CCList
end
]}
{4 Containers.data}
{b findlib name}: containers.data
Various data structures.
{!modules:
CCBitField
CCBV
CCCache
CCDeque
CCFQueue
CCFlatHashtbl
CCGraph
CCHashSet
CCHashTrie
CCHet
CCImmutArray
CCIntMap
CCMixmap
CCMixset
CCMixtbl
CCMultiMap
CCMultiSet
CCPersistentArray
CCPersistentHashtbl
CCRAL
CCRingBuffer
CCSimple_queue
CCTrie
CCWBTree
}
{4 Containers.unix}
Helpers that depend on {!Unix}, e.g. to spawn sub-processes.
{!modules: CCUnix}
{4 Containers.sexp}
A small S-expression library. The interface is relatively unstable, but
the main type ([CCSexp.t]) isn't.
{!modules:
CCSexp
}
{4 Containers.iter}
Iterators:
{!modules:
CCKList
CCKTree
CCLazy_list}
{4 String}
containers.string has been removed. Some of its functionality is present
in {!CCString}; some in other libraries such as [Spelll].
{4 Bigarrays}
containers.bigarray has been removed. Use the [Bigstring] library for
arrays of bytes.
{4 Advanced}
containers.advanced has been removed. Use [OLinq] to replace some of its
functionality.
{4 Misc}
Moved to its own repository.
{4 Lwt}
Moved to its own repository
{4 Thread Helpers}
{b findlib name}: containers.thread
Modules related to the use of [Thread].
{!modules:
CCBlockingQueue
CCLock
CCPool
CCSemaphore
CCThread
CCTimer
}
{2 Index}
{!indexlist}

41
dune
View file

@ -1,41 +0,0 @@
(rule
(targets README.md.corrected)
(deps
(package containers-data)
./src/mdx_runner.exe)
(enabled_if
(= %{system} "linux"))
(action
(run ./src/mdx_runner.exe)))
(rule
(alias runtest)
(package containers-data)
(enabled_if
(= %{system} "linux"))
(locks /ctest)
(action
(diff README.md README.md.corrected)))
(env
(_
(flags
:standard
-warn-error
-a+8
-w
-32-48-60-70
-w
+a-4-40-42-44-70
-color
always
-safe-string
-strict-sequence)
(ocamlopt_flags
:standard
-O3
-unbox-closures
-unbox-closures-factor
20
-inline
100)))

View file

@ -1,60 +0,0 @@
(lang dune 3.0)
(name containers)
(generate_opam_files true)
(version 3.17)
(authors c-cube)
(maintainers c-cube)
(license BSD-2-Clause)
(homepage "https://github.com/c-cube/ocaml-containers/")
(source
(github c-cube/ocaml-containers))
(package
(name containers)
(synopsis
"A modular, clean and powerful extension of the OCaml standard library")
(tags
(stdlib containers iterators list heap queue))
(depends
(ocaml
(>= 4.08))
either
dune-configurator
(qcheck-core
(and
(>= 0.18)
:with-test))
(yojson :with-test)
(iter :with-test)
(gen :with-test)
(csexp :with-test)
(uutf :with-test)
(odoc :with-doc))
(depopts base-unix base-threads))
(package
(name containers-data)
(synopsis "A set of advanced datatypes for containers")
(tags
(containers RAL function vector okasaki))
(depends
(ocaml
(>= 4.08))
(containers
(= :version))
(qcheck-core
(and
(>= 0.18)
:with-test))
(iter :with-test)
(gen :with-test)
(mdx :with-test)
(odoc :with-doc)))

22
examples/bencode_write.ml Normal file
View file

@ -0,0 +1,22 @@
(** Write 10_000 Bencode values on the given file *)
(* write n times the same value in the file *)
let write_values file n =
let out = BencodeOnDisk.open_out file in
Printf.printf "[%d] opened file\n" (Unix.getpid ());
let v = Bencode.(L [I 0; I 1; S "foo"]) in
for i = 0 to n-1 do
Printf.printf "[%d] iteration %d\n" (Unix.getpid ()) i;
flush stdout;
BencodeOnDisk.write out v;
done;
BencodeOnDisk.close_out out;
Printf.printf "done\n";
()
let _ =
let file = Sys.argv.(1) in
Printf.printf "[%d] start: write to %s\n" (Unix.getpid ()) file;
flush stdout;
write_values file 100

View file

@ -1,80 +0,0 @@
(* parse IRC logs *)
type datetime = {
year: int;
month: int;
day: int;
hour: int;
min: int;
sec: int;
}
let pp_datetime out d =
let { year; month; day; hour; min; sec } = d in
CCFormat.(
fprintf out "{y=%d;M=%d;d=%d;h=%d;m=%d;s=%d}" year month day hour min sec)
type msg = {
timestamp: datetime;
user: string;
msg: string;
}
let pp_msg out m =
CCFormat.fprintf out "{@[time=%a;@ user=%S;@ msg=%S@]}" pp_datetime
m.timestamp m.user m.msg
open CCParse
let p_datetime : datetime t =
let int = U.int in
let* date, time = split_2 ~on_char:' ' in
let* y, m, d = recurse date (split_3 ~on_char:'-') in
let* year = recurse y int in
let* month = recurse m int in
let* day = recurse d int in
let* hour, min, sec =
recurse time
(let* hour = int in
char ':'
*> let* min = int in
char ':'
*> let+ sec = int in
hour, min, sec)
in
let dt = { year; month; day; hour; min; sec } in
return dt
let p_line =
let* line = lookahead all in
if Slice.is_empty line then
return None
else
let* fields = split_list ~on_char:'\t' in
match fields with
| [ date; user; rest ] ->
let+ timestamp = recurse date p_datetime
and+ user =
recurse user
(chars_if (function
| '>' -> false
| _ -> true))
and+ msg = recurse rest (all_str >|= String.trim) in
Some { timestamp; user; msg }
| _ ->
failf "expected 3 fields, got [%s]"
(String.concat ";" @@ List.map String.escaped
@@ List.map Slice.to_string fields)
let p_file = each_line (parsing "line" p_line) >|= CCList.keep_some
let () =
let s = CCIO.File.read_exn Sys.argv.(1) in
match parse_string p_file s with
| Ok l ->
Format.printf "parsed:@.";
List.iter (Format.printf "%a@." pp_msg) l
| Error e ->
Format.printf "parse error: %s@." e;
exit 1

View file

@ -1,73 +0,0 @@
open CCParse
type sexp =
| Atom of string
| List of sexp list
let rec pp_sexpr out (s : sexp) : unit =
match s with
| Atom s -> Format.fprintf out "%S" s
| List l ->
Format.fprintf out "(@[";
List.iteri
(fun i s ->
if i > 0 then Format.fprintf out "@ ";
pp_sexpr out s)
l;
Format.fprintf out "@])"
let skip_white_and_comments =
fix @@ fun self ->
skip_white
*> try_or (char ';')
~f:(fun _ ->
skip_chars (function
| '\n' -> false
| _ -> true)
*> self)
~else_:(return ())
let atom =
chars_fold_transduce `Start ~f:(fun acc c ->
match acc, c with
| `Start, '"' -> `Continue `In_quote
| `Start, (' ' | '\t' | '\n' | '(' | ')' | ';') -> `Fail "atom"
| `Normal, (' ' | '\t' | '\n' | '(' | ')' | ';') -> `Stop
| `Done, _ -> `Stop
| `In_quote, '"' -> `Continue `Done (* consume *)
| `In_quote, '\\' -> `Continue `Escape
| `In_quote, c -> `Yield (`In_quote, c)
| `Escape, 'n' -> `Yield (`In_quote, '\n')
| `Escape, 't' -> `Yield (`In_quote, '\t')
| `Escape, '"' -> `Yield (`In_quote, '"')
| `Escape, '\\' -> `Yield (`In_quote, '\\')
| `Escape, c -> `Fail (Printf.sprintf "unknown escape code \\%c" c)
| (`Start | `Normal), c -> `Yield (`Normal, c)
| _ -> `Fail "invalid atom")
>>= function
| `In_quote, _ -> fail "unclosed \""
| `Escape, _ -> fail "unfinished escape sequence"
| _, "" -> fail "expected non-empty atom"
| _, s -> return (Atom s)
let psexp =
fix @@ fun self ->
skip_white_and_comments
*> try_or (char '(')
~f:(fun _ ->
sep ~by:skip_white_and_comments self
<* skip_white_and_comments <* char ')'
>|= fun l -> List l)
~else_:atom
let psexp_l = many_until ~until:(skip_white_and_comments *> eoi) psexp
let () =
let s = CCIO.File.read_exn Sys.argv.(1) in
match parse_string psexp_l s with
| Ok l ->
Format.printf "parsed:@.";
List.iter (Format.printf "%a@." pp_sexpr) l
| Error e ->
Format.printf "parse error: %s@." e;
exit 1

26
examples/cgi/web_pwd.ml Normal file
View file

@ -0,0 +1,26 @@
(** Export the list of files in a directory *)
let dir = "/tmp/"
(* list of files in a dir *)
let lsdir dir =
let d = Unix.opendir dir in
let l = ref [] in
begin try while true do
l := Unix.readdir d :: !l
done with End_of_file -> Unix.closedir d
end;
!l
let export dir =
let l = lsdir dir in
ToWeb.HTML.(concat
[ h1 (str ("files in "^ dir))
; list (List.map str l)
])
let state = ToWeb.State.create dir ~export
let _ =
ToWeb.serve_state ~sockfile:"/tmp/foo.sock" state

20
examples/collatz.ml Normal file
View file

@ -0,0 +1,20 @@
(** Display the graph of the collatz conjecture, starting from the given int *)
let g = LazyGraph.map
~edges:(fun () -> [])
~vertices:(fun i -> [`Label (string_of_int i)])
LazyGraph.collatz_graph
let collatz n filename =
Format.printf "print graph to %s@." filename;
let out = open_out filename in
let fmt = Format.formatter_of_out_channel out in
LazyGraph.Dot.pp ~name:"collatz" g fmt (Sequence.singleton n);
Format.pp_print_flush fmt ();
close_out out
let _ =
if Array.length Sys.argv < 3
then (Format.printf "use: collatz num file@."; exit 0)
else collatz (int_of_string Sys.argv.(1)) Sys.argv.(2)

83
examples/crawl.ml Normal file
View file

@ -0,0 +1,83 @@
(** Crawl the web to find shortest path between two urls *)
open Batteries
let pool = Future.Pool.create ~timeout:15. ~size:50
let split_lines s = String.nsplit s ~by:"\n"
let get_and_parse url =
let cmd = Format.sprintf "wget -q '%s' -O - | grep -o 'http\\(s\\)\\?://[^ \"]\\+'" url in
let content = Future.spawn_process ?stdin:None ~pool ~cmd in
content
|> Future.map (fun (_, stdout, _) -> stdout)
|> Future.map split_lines
|> Batteries.tap (fun lines ->
Future.on_success lines (fun lines -> Format.printf "downloaded %s (%d urls)@." url (List.length lines)))
type page = string * (string list Future.t)
(** The web graph; its vertices are annotated by futures of the content *)
let g : (page, string, unit) LazyGraph.t =
let force (url, future) =
Format.printf "force %s@." url;
let urls =
try Future.get future |> List.map (fun url -> (), (url, get_and_parse url))
with e -> [] in
let edges = Gen.of_list urls in
(* need to parse the page to get the urls *)
LazyGraph.Node ((url, future), url, edges)
in LazyGraph.make
~eq:(fun (url1,_) (url2,_) -> url1 = url2)
~hash:(fun (url,_) -> Hashtbl.hash url)
force
let pp_path fmt path =
List.print ~sep:"\n"
(fun fmt ((u1,_), (), (u2,_)) ->
String.print fmt u1; String.print fmt " -> "; String.print fmt u2)
fmt path
(* seek a path from the first url to the second *)
let path_between from into =
Format.printf "seek path from %s to %s@." from into;
let on_explore (url,_) = Format.printf " explore %s...@." url in
try
let cost, path = LazyGraph.dijkstra ~on_explore g
(from, get_and_parse from) (into, get_and_parse into) in
Printf.printf "found path (cost %f):\n%a\n" cost pp_path path
with Not_found ->
Format.printf "no path could be found@."
let print_limit file start depth =
Format.printf "print into %s webgraph starting from %s, up to depth %d@."
file start depth;
let start = start, get_and_parse start in
let g' = LazyGraph.limit_depth g depth (Gen.singleton start) in
let g'' = LazyGraph.map ~vertices:(fun v -> [`Label v]) ~edges:(fun _ -> []) g' in
let out = Format.formatter_of_out_channel (open_out file) in
LazyGraph.Dot.pp ~name:"web" g'' out (Gen.singleton start);
Format.pp_print_flush out ();
()
let _ =
let timer = Future.Timer.create () in
let rec ping () =
Format.printf "*** ping! (size of pool: %d)@." (Future.Pool.size pool);
Future.Timer.schedule_in timer 10. ping
in ping ()
let print_usage () =
Format.printf "usage: crawl path url1 url2@.";
Format.printf "usage: crawl print file url depth@.";
()
let _ =
match Sys.argv with
| [|_; "print"; file; url; depth|] ->
print_limit file url (int_of_string depth)
| [|_; "path"; from; into|] ->
path_between from into
| _ ->
print_usage ()

View file

@ -1,49 +0,0 @@
(executables
(names id_sexp ccparse_sexp ccparse_irclogs)
(libraries containers)
(flags :standard -warn-error -a+8))
(rule
(alias runtest)
(locks /ctest)
(deps
(source_tree test_data))
(action
(ignore-stdout
(run ./id_sexp.exe test_data/benchpress.sexp))))
(rule
(alias runtest)
(locks /ctest)
(deps
(source_tree test_data))
(action
(ignore-stdout
(run ./ccparse_sexp.exe test_data/benchpress.sexp))))
(rule
(targets ccparse_irclogs.ml)
(enabled_if
(>= %{ocaml_version} "4.08"))
(action
(copy ccparse_irclogs_real.cond.ml %{targets})))
(rule
(targets ccparse_irclogs.ml)
(enabled_if
(< %{ocaml_version} "4.08"))
(action
(with-stdout-to
%{targets}
(run echo "let() = print_endline {|ok|}"))))
(rule
(alias runtest)
(locks /ctest)
(deps
(source_tree test_data))
(enabled_if
(>= %{ocaml_version} "4.08"))
(action
(ignore-stdout
(run ./ccparse_irclogs.exe test_data/irc-logs.txt))))

View file

@ -1,14 +1,20 @@
let pp_sexp s =
match s with open Result
| Ok l -> List.iter (fun s -> Format.printf "@[%a@]@." CCSexp.pp s) l
| Error msg -> Format.printf "error: %s@." msg let pp_sexp s = match s with
| Ok l ->
List.iter
(fun s -> Format.printf "@[%a@]@." CCSexp.pp s)
l
| Error msg ->
Format.printf "error: %s@." msg
let () = let () =
match Sys.argv with match Sys.argv with
| [| _ |] -> | [| _ |] ->
let s = CCSexp.parse_chan_list stdin in let s = CCSexp.parse_chan_list stdin in
pp_sexp s pp_sexp s
| [| _; file |] -> | [| _; file |] ->
let s = CCSexp.parse_file_list file in let s = CCSexp.parse_file_list file in
pp_sexp s pp_sexp s
| _ -> failwith "usage: id_sexp [file]" | _ -> failwith "usage: id_sexp [file]"

112
examples/lambda.ml Normal file
View file

@ -0,0 +1,112 @@
(** Example of printing trees: lambda-term evaluation *)
type term =
| Lambda of string * term
| App of term * term
| Var of string
let _gensym =
let r = ref 0 in
fun () ->
let s = Printf.sprintf "x%d" !r in
incr r;
s
module SSet = Set.Make(String)
module SMap = Map.Make(String)
let rec fvars t = match t with
| Var s -> SSet.singleton s
| Lambda (v,t') ->
let set' = fvars t' in
SSet.remove v set'
| App (t1, t2) -> SSet.union (fvars t1) (fvars t2)
(* replace [var] with the term [by] *)
let rec replace t ~var ~by = match t with
| Var s -> if s=var then by else t
| App (t1,t2) -> App (replace t1 ~var ~by, replace t2 ~var ~by)
| Lambda (v, t') when v=var -> t (* no risk *)
| Lambda (v, t') -> Lambda (v, replace t' ~var ~by)
(* rename [t] so that [var] doesn't occur in it *)
let rename ~var t =
if SSet.mem var (fvars t)
then replace t ~var ~by:(Var (_gensym ()))
else t
let (>>=) o f = match o with
| None -> None
| Some x -> f x
let rec one_step t = match t with
| App (Lambda (var, t1), t2) ->
let t2' = rename ~var t2 in
Some (replace t1 ~var ~by:t2')
| App (t1, t2) ->
begin match one_step t1 with
| None ->
one_step t2 >>= fun t2' ->
Some (App (t1,t2'))
| Some t1' ->
Some (App (t1',t2))
end
| Var _ -> None
| Lambda (v,t') ->
one_step t' >>= fun t'' ->
Some (Lambda (v, t''))
let normal_form t =
let rec aux acc t = match one_step t with
| None -> List.rev (t::acc)
| Some t' -> aux (t::acc) t'
in
aux [] t
let _split_fuel f =
assert (f>=2);
if f=2 then 1,1
else
let x = 1+Random.int (f-1) in
f-x, x
let _random_var () =
let v = [| "x"; "y"; "z"; "u"; "w" |] in
v.(Random.int (Array.length v))
let _choose_var ~vars = match vars with
| [] -> Var (_random_var ())
| _::_ ->
let i = Random.int (List.length vars) in
List.nth vars i
let rec _random_term fuel vars =
match Random.int 2 with
| _ when fuel = 1 -> _choose_var ~vars
| 0 ->
let f1,f2 = _split_fuel fuel in
App (_random_term f1 vars, _random_term f2 vars)
| 1 ->
let v = _random_var () in
Lambda (v, _random_term (fuel-1) (Var v::vars))
| _ -> assert false
let print_term t =
PrintBox.mk_tree
(function
| Var v -> PrintBox.line v, []
| App (t1,t2) -> PrintBox.line "app", [t1;t2]
| Lambda (v,t') -> PrintBox.line "lambda", [Var v; t']
) t
let print_reduction t =
let l = normal_form t in
let l = List.map (fun t -> PrintBox.pad (print_term t)) l in
PrintBox.vlist ~bars:false l
let () =
Random.self_init ();
let t = _random_term (5 + Random.int 20) [] in
PrintBox.output ~indent:2 stdout (print_reduction t)

67
examples/mem_size.ml Normal file
View file

@ -0,0 +1,67 @@
(** Compute the memory footprint of a value (and its subvalues). Reference is
http://rwmj.wordpress.com/2009/08/05/ocaml-internals-part-2-strings-and-other-types/ *)
(** A graph vertex is an Obj.t value *)
let graph =
let force x =
if Obj.is_block x
then
let children = Sequence.map (fun i -> i, Obj.field x i) (0--(Obj.size x - 1)) in
LazyGraph.Node (x, Obj.tag x, children)
else
LazyGraph.Node (x, Obj.obj x, Sequence.empty)
in LazyGraph.make ~eq:(==) force
let word_size = Sys.word_size / 8
let size x =
if Obj.is_block x
then (1+Obj.size x) * word_size
else word_size
let compute_size x =
let o = Obj.repr x in
let vertices = LazyGraph.bfs graph o in
Sequence.fold (fun sum (o',_,_) -> size o' + sum) 0 vertices
let print_val fmt x =
let o = Obj.repr x in
let graph' = LazyGraph.map ~edges:(fun i -> [`Label (string_of_int i)])
~vertices:(fun v -> [`Label (string_of_int v); `Shape "box"]) graph in
LazyGraph.Dot.pp ~name:"value" graph' fmt (Sequence.singleton o)
let print_val_file filename x =
let out = open_out filename in
let fmt = Format.formatter_of_out_channel out in
print_val fmt x;
Format.pp_print_flush fmt ();
close_out out
let process_val ~name x =
print_val_file (Format.sprintf "/tmp/%s.dot" name) x;
Format.printf "size of val is %d@." (compute_size x)
module ISet = Set.Make(struct type t = int let compare = compare end)
let mk_circ n =
let start = Sequence.to_list (1--n) in
(* make the end of the list point to its beginning *)
let rec cycle l = match l with
| [] -> assert false
| [_] -> Obj.set_field (Obj.repr l) 1 (Obj.repr start)
| _::l' -> cycle l'
in
cycle start;
start
let _ =
let s = Sequence.fold (fun s x -> ISet.add x s) ISet.empty (1--100) in
process_val ~name:"foo" s;
let l = Sequence.to_list (Sequence.map (fun i -> Sequence.to_list (i--(i+42)))
(Sequence.of_list [0;100;1000])) in
process_val ~name:"bar" l;
let l' = mk_circ 100 in
process_val ~name:"baaz" l';
()

View file

@ -1,13 +0,0 @@
(prover
(name msat)
(synopsis "msat for pure sat problems")
(version "git:.")
(sat "^Sat")
(unsat "^Unsat")
(cmd "$cur_dir/../msat.exe -time $timeout $file"))
(dir
(path $cur_dir)
(pattern ".*\\.cnf")
(expect (const unknown)))

View file

@ -1,777 +0,0 @@
2021-06-04 00:50:44 kluk> How do I start using DynArray from the ocaml command line?
2021-06-04 00:50:51 kluk> I have already done opam install extlib
2021-06-04 00:51:12 kluk> I am a newbie at OCaml
2021-06-04 05:18:03 dockerusocamlus> Hello! I'm minimizing an Alpine-based Docker image with OCaml installed via opam, and I'm trying to understand if I could erase some files to save some space. Basically, trying to understand if they are needed only on special situations, or if that would cause issues for users of the Docker image.
2021-06-04 05:19:46 dockerusocamlus> For instance, in this image, I have file ~/.opam/<version>/lib/ocaml/expunge, which take 15 MB of space. I don't think I have ever used it, but I don't know if it's internally used by some other OCaml process.
2021-06-04 05:28:12 dockerusocamlus> I don't have much documentation about it, and grepping ocaml's sources only shows a few occurrences. It seems related to the installation of the OCaml compiler itself, but even after removing it, I'm still able to do a `opam switch create` to install a different compiler, so... I guess it's fine to remove it?
2021-06-04 05:36:13 octachron> This is a compiler tool which is used to build REPLs. It is also used by utop.
2021-06-04 05:42:54 dockerusocamlus> Thanks!
2021-06-04 08:10:44 superherointj> Need some feedback on a minimalistic lwt demo: https://github.com/superherointj/lwt-demo1
2021-06-04 08:38:37 d_bot> <superherointj> Just solved it. I must be really tired.
2021-06-04 09:49:45 d_bot> <superherointj> Can anybody point me to a good article/information on incompatible ppx drivers (ppxlib and ocaml-migrate-parsetree)?
2021-06-04 09:49:46 d_bot> <superherointj> I have read already the saga blog post, but I am missing something.
2021-06-04 09:49:47 d_bot> <superherointj> I want to build my old project. I'm trying to replicate problem atm on a demo.
2021-06-04 09:50:25 companion_cube> people are supposed to use ppxlib, that's all I know
2021-06-04 09:51:25 d_bot> <superherointj> Any example?
2021-06-04 09:51:51 companion_cube> https://github.com/ocaml-ppx/ppx_deriving I guess?
2021-06-04 09:52:40 d_bot> <superherointj> Found this:
2021-06-04 09:52:41 d_bot> <superherointj> https://ppxlib.readthedocs.io/_/downloads/en/stable/pdf/
2021-06-04 09:57:49 d_bot> <EduardoRFS> Why does OCaml not optimizes this in a noop? Even under flambda and -O3
2021-06-04 09:57:49 d_bot> <EduardoRFS>
2021-06-04 09:57:51 d_bot> <EduardoRFS> ```ocaml
2021-06-04 09:57:52 d_bot> <EduardoRFS> let f (a, b) = (a, b)
2021-06-04 09:57:53 d_bot> <EduardoRFS> ```
2021-06-04 10:00:07 @adrien> it returns a new tuple, not the same one
2021-06-04 10:00:37 @adrien> let x = (1,2);; let f (a, b) = (a, b);; let y = f x;; y == x;;
2021-06-04 10:00:41 d_bot> <EduardoRFS> the question is why? It would change the `==` behavior but it's already not defined from what I remember
2021-06-04 10:01:06 d_bot> <EduardoRFS> it behaves differently in bytecode, native and IIRC it's also different in flambda
2021-06-04 10:01:14 companion_cube> I agree it'd be a valid optim
2021-06-04 10:02:19 d_bot> <EduardoRFS> This is especiall try for the case of different types and pattern matching but that generates identical data in memory, like
2021-06-04 10:02:20 d_bot> <EduardoRFS>
2021-06-04 10:02:21 d_bot> <EduardoRFS> ```ocaml
2021-06-04 10:02:22 d_bot> <EduardoRFS> type a = | A(int)
2021-06-04 10:02:24 d_bot> <EduardoRFS> type b = B(int)
2021-06-04 10:02:25 d_bot> <EduardoRFS> let f = function | A v -> B v
2021-06-04 10:02:26 d_bot> <EduardoRFS> ```
2021-06-04 10:02:36 @adrien> I get the same behaviour in native
2021-06-04 10:03:11 @adrien> and you can do f u = u
2021-06-04 10:03:13 companion_cube> @eduardors these are only the same by accident though
2021-06-04 10:03:18 companion_cube> seems far less useful as an optim
2021-06-04 10:03:18 zozozo> see https://github.com/ocaml/ocaml/pull/8958
2021-06-04 10:03:22 d_bot> <EduardoRFS> yes but the compiler knows it
2021-06-04 10:03:32 @adrien> not sure how is the generated code but in that case it's not a new tuple
2021-06-04 10:04:02 zozozo> there is a PR to do pretty much that (the link I posted above)
2021-06-04 10:04:05 theblatte> I keep writing functions like `let f ((a,b) as x0) = let a' = g a in let b' = g b in if a == a' && b == b' then x0 else (a', b')`
2021-06-04 10:04:07 d_bot> <EduardoRFS> in this case yes, but not all cases, I'm not asking about this specific tuple, I'm asking more about identical blocks that are known to be always identical
2021-06-04 10:04:13 companion_cube> I don't think it's a very useful optimization to see if per chance two different variants of different types happen to have the same binary representation
2021-06-04 10:04:33 companion_cube> more important stuff is to eliminate temporaries imho
2021-06-04 10:04:41 companion_cube> like a tuple built just to be deconstructed in the same function
2021-06-04 10:04:45 companion_cube> (or an option…)
2021-06-04 10:04:53 zozozo> companion_cube: what do you mean by "temporaries"?
2021-06-04 10:05:05 d_bot> <EduardoRFS> temporary allocations IIUC
2021-06-04 10:05:06 companion_cube> data that doesn't escape the current function :p
2021-06-04 10:05:09 zozozo> companion_cube: ah, well, avoiding these is more or less exactly the job of flambda, ^^
2021-06-04 10:05:11 companion_cube> (after inlining)
2021-06-04 10:05:12 companion_cube> yeah I know
2021-06-04 10:05:17 companion_cube> godspeed to you zozozo
2021-06-04 10:05:30 zozozo> ^^
2021-06-04 10:05:55 zozozo> @EduardoRFS : did you look at https://github.com/ocaml/ocaml/pull/8958 ?
2021-06-04 10:07:07 d_bot> <EduardoRFS> I'm looking on it, the argument of not being predictable is sad, it's a flat allocation reduction, no hidden allocation, not trying to make non efficient code efficient, but trying to make code that is efficient as possible more efficient
2021-06-04 10:07:34 zozozo> companion_cube: also, note that sometimes, because of type subtleties, you need to write the "identity" function, as a pattern match that then reconstructs exactly the same value, but with a slightly different type (thing GADTs), in such cases, being able to detect that a switch returns exactly its argument, is a nice improvements, and you can't really write it differently because of the types
2021-06-04 10:07:36 @adrien> well, as theblatte said, the "as" construct should help for that case
2021-06-04 10:08:04 d_bot> <EduardoRFS> that's exactly the case zozozo, for a lot of code in ocaml-migrate-types
2021-06-04 10:08:11 theblatte> zozozo: yes!
2021-06-04 10:08:19 zozozo> the advantage of the PR I linked is that it can trigger in cases where one cannot write code using "as"
2021-06-04 10:08:27 theblatte> much sad when that happens
2021-06-04 10:08:30 d_bot> <EduardoRFS> "as"?
2021-06-04 10:08:53 zozozo> @EduardoRFS : `let f ((a, b) as pair) = pair`
2021-06-04 10:09:13 d_bot> <EduardoRFS> oh but that works only for structural types
2021-06-04 10:09:21 companion_cube> zozozo: good argument against GADTs ;)
2021-06-04 10:09:34 d_bot> <EduardoRFS> companion_cube loves GADTs
2021-06-04 10:09:42 companion_cube> heh, in small doses
2021-06-04 10:09:47 theblatte> companion_cube: no need for GADTs! https://github.com/facebook/infer/blob/cfed4c4fa0c99ab1f42683bb92df76c8c8434e79/infer/src/pulse/PulseSummary.ml#L56
2021-06-04 10:10:03 olle> as?
2021-06-04 10:10:06 olle> new keyword?
2021-06-04 10:10:13 companion_cube> wait, theblatte, why
2021-06-04 10:10:14 theblatte> eg phantom type parameters
2021-06-04 10:10:18 companion_cube> ah yes
2021-06-04 10:10:29 companion_cube> it's unfortunate
2021-06-04 10:10:34 theblatte> (in my case not phantom but "phantom" because it doesn't show up in some of the variants)
2021-06-04 10:10:37 companion_cube> but it's the same constructors in this case.
2021-06-04 10:10:38 zozozo> companion_cube: gadts are useful *sometimes*
2021-06-04 10:10:40 d_bot> <EduardoRFS> I wonder if #8958 would be better as lambda layer
2021-06-04 10:10:44 d_bot> <EduardoRFS> but tempting to rebase it ;/
2021-06-04 10:11:25 zozozo> @EduardoRFS : the problem is that if you do that at lambda level, you miss out on situations where it happens after some inlining/simplification
2021-06-04 10:11:47 d_bot> <EduardoRFS> yeah but you ensure same behavior between all backends
2021-06-04 10:11:50 zozozo> (also, the code of lambda simplifications is quite a mess from what I hear)
2021-06-04 10:12:33 theblatte> companion_cube: same constructors: yes, personally I would only care about preserving physical equality when the objects are actually equal but ymmv
2021-06-04 10:12:34 zozozo> well.. there is now a pass specifically designed to implement optimizations, so why not use it ?
2021-06-04 10:13:05 theblatte> I've seen several examples where it would have a material effect on perf
2021-06-04 10:13:08 d_bot> <EduardoRFS> But the pass should not change behavior of code unless it provides a fallback, this is how I see most of it
2021-06-04 10:13:13 d_bot> <EduardoRFS> maybe Sys.opaque_identity would ignore it
2021-06-04 10:13:32 d_bot> <EduardoRFS> can we deprecate ==? That seems like a better idea overall
2021-06-04 10:13:34 companion_cube> zozozo: because it only works for native?
2021-06-04 10:13:41 companion_cube> ahahah
2021-06-04 10:13:47 companion_cube> removing == kills perf for other programs
2021-06-04 10:14:01 theblatte> #8958 ftw, I didn't know there'd been such a PR in flight for such a long time
2021-06-04 10:14:04 zozozo> companion_cube: well, bytecode is pretty much meant to not care about performance, so from that point of view it's not unreasonable
2021-06-04 10:14:05 d_bot> <EduardoRFS> not removing it, deprecating it, keep it under Obj.xx
2021-06-04 10:14:34 theblatte> == is an important part of the language, not an extension
2021-06-04 10:14:41 zozozo> the *good* solution would be to change the bytecode generation to use the result of flambda
2021-06-04 10:14:56 zozozo> the semantics of "==" is largely not officially specified
2021-06-04 10:14:56 d_bot> <EduardoRFS> NAH
2021-06-04 10:15:10 theblatte> but not a bad idea to not give it such an easily-confused name :p eg use "phys_equal" instead
2021-06-04 10:15:12 zozozo> and for any non-mutable record, there are next to no guarantees about "=="
2021-06-04 10:15:26 d_bot> <EduardoRFS> unless we had a blazing fast flambda pass, bytecode is so fast right now
2021-06-04 10:16:22 d_bot> <EduardoRFS> == is not exactly part of the language in many ways, and it's known to behave differently depending on the backend which should never happen for a specified feature of the language
2021-06-04 10:16:30 zozozo> @EduardoRFS: are you talking about compilation time or runtime of the compild program ?
2021-06-04 10:16:35 d_bot> <EduardoRFS> compilation time
2021-06-04 10:16:36 companion_cube> zozozo: I wish I could agree
2021-06-04 10:16:40 companion_cube> but some of us are stuck with bytecode
2021-06-04 10:16:45 d_bot> <EduardoRFS> bytecode is slow in runtime, really slow
2021-06-04 10:16:46 companion_cube> because that's the only official toplevel for now
2021-06-04 10:17:10 d_bot> <EduardoRFS> but bytecode generated from flambda would still work with the toplevel
2021-06-04 10:17:16 zozozo> companion_cube: yeah, but sometimes with others in my team, we talk about making it so that bytecode is generated after the flambda pass, which would solve all problems (if we can make it work)
2021-06-04 10:17:21 companion_cube> sure
2021-06-04 10:17:36 companion_cube> I mean in the future maybe we'll also have a JIT
2021-06-04 10:17:42 d_bot> <EduardoRFS> there is any plan on deprecating closure middle end?
2021-06-04 10:17:45 companion_cube> but for now it's not like there's a choice, and there's basically 0 optims on bytecode
2021-06-04 10:17:47 companion_cube> which… ugh
2021-06-04 10:19:26 d_bot> <dinosaure> it remmembers me one time when people compared ocsigenserver and http servers and used the bytecode version accidentally and say, OCaml is so bad
2021-06-04 10:19:34 companion_cube> :D
2021-06-04 10:19:38 d_bot> <EduardoRFS> D:
2021-06-04 10:19:49 companion_cube> or even using dune without --profile=release
2021-06-04 10:19:53 companion_cube> bye bye optims
2021-06-04 10:19:58 d_bot> <EduardoRFS> TEZOS IS RUNNING WITHOUT PROFILE=RELEASE
2021-06-04 10:20:25 d_bot> <EduardoRFS> even worse it is benchmarked without profile=release
2021-06-04 10:20:33 companion_cube> hu, weirder
2021-06-04 10:21:18 zozozo> well, if the switch is not using flambda, I don't think the difference is that important between the dev and release profiles
2021-06-04 10:22:34 companion_cube> err, you still have a bit of cross module inlining, don't you?
2021-06-04 10:22:39 companion_cube> with normal ocamlopt
2021-06-04 10:22:54 zozozo> I'm not sure
2021-06-04 10:22:55 d_bot> <EduardoRFS> yeah it makes difference, I benchmarked it, around 30% boost on some smart contracts
2021-06-04 10:23:06 d_bot> <EduardoRFS> dune without profile=release runs under -opaque
2021-06-04 10:23:10 companion_cube> I think it does, including for stuff like externals
2021-06-04 10:23:16 companion_cube> exactly
2021-06-04 10:23:25 companion_cube> --profile=release brings you back to normal behavior
2021-06-04 10:23:26 zozozo> I think (but I'm not sure) the only thing cross-inlined would be externals, but those are in the .mlis so no need for cross-optimization actually
2021-06-04 10:23:30 d_bot> <EduardoRFS> externals rely on the interface, so it doesn't depend on profile=release
2021-06-04 10:23:50 companion_cube> zozozo: but the .cmx ?
2021-06-04 10:24:00 theblatte> is profile=release different than passing -O3 to ocamlopt??
2021-06-04 10:24:05 zozozo> ah, maybe the small functions that closure unconditionally inline are inliend cross-modules by vanilla ocamlopt
2021-06-04 10:24:17 d_bot> <EduardoRFS> it is, because without profile=release you're under -opaque
2021-06-04 10:24:30 theblatte> whaaaat
2021-06-04 10:24:40 theblatte> :o
2021-06-04 10:24:44 d_bot> <EduardoRFS> that's the only way to achieve blazing fast build speed
2021-06-04 10:24:53 companion_cube> zozozo: the functions marked "inline" in .cmx files
2021-06-04 10:24:56 d_bot> <EduardoRFS> yup, small functions like having `Module.equal` are not inlined and Module.equal a lot of times is literally a single cnstruction
2021-06-04 10:25:09 theblatte> blazing fast = 6x slower than without -O3 ^^
2021-06-04 10:25:11 companion_cube> that's what I was talking about
2021-06-04 10:25:21 zozozo> companion_cube: indeed, ^^
2021-06-04 10:25:30 companion_cube> so it can make a big difference :)
2021-06-04 10:25:35 companion_cube> even without flambda
2021-06-04 10:25:45 theblatte> ohhh, recently-ish we noticed marking some functor arguments as [@inline] made a big difference
2021-06-04 10:25:52 companion_cube> :D
2021-06-04 10:25:59 zozozo> that's not surprising
2021-06-04 10:26:04 theblatte> is that sort of thing (adding @inline) not needed with flambda + release profile?
2021-06-04 10:26:25 theblatte> or is that independent?
2021-06-04 10:26:26 companion_cube> it still gives you better control
2021-06-04 10:26:34 zozozo> iirc, flambda tries as much as possibvle to inline functor applicaiton that are at toplevel, so you shouldn't need the annotations in that particular case
2021-06-04 10:26:51 companion_cube> do a lot of people use flambda1 in production?!
2021-06-04 10:26:59 zozozo> companion_cube: jane street i guess ?
2021-06-04 10:27:07 companion_cube> ahah maybe they have enough RAM
2021-06-04 10:27:16 zozozo> also, the binary release of dolmen is now compiled with flambda, :D
2021-06-04 10:27:18 companion_cube> I stopped using it years ago
2021-06-04 10:27:18 theblatte> infer is 30% faster with flambda, so you bet
2021-06-04 10:27:32 companion_cube> wow
2021-06-04 10:27:37 companion_cube> well can't wait for flambda2
2021-06-04 10:28:01 companion_cube> anyway, the point of --profile=release is to tell dune to not block optimizations, it doesn't enable new ones
2021-06-04 10:28:05 companion_cube> for that you can use ocamlopt_flags
2021-06-04 10:28:13 d_bot> <EduardoRFS> tezos is another 20% faster on flambda
2021-06-04 10:28:15 zozozo> we're trying very hard on making it so that flambda2 is as fast as possible, but it's hard sometimes
2021-06-04 10:28:27 companion_cube> zozozo: it's not just a question of "fast"
2021-06-04 10:28:35 companion_cube> it's also "not gobble up RAM on bad cases"
2021-06-04 10:28:38 theblatte> yes but I'm trying to understand if adding --profile=release will make a difference
2021-06-04 10:28:51 theblatte> I'll try that
2021-06-04 10:29:01 companion_cube> so, -p foo already switches to release mode
2021-06-04 10:29:12 companion_cube> it's only if you use `dune build @all` and that kind of stuff that it matters
2021-06-04 10:29:21 zozozo> companion_cube: right, can you send me (if you recall), the packages that were not working 'or taking ut too much RAM) ?
2021-06-04 10:29:24 companion_cube> it makes compilation slower (removes -opaque) but enables optimization
2021-06-04 10:29:27 companion_cube> zozozo: at least dose3
2021-06-04 10:29:30 companion_cube> that was the blocker
2021-06-04 10:29:32 zozozo> so that we can at least try and see what happens with flamdba2
2021-06-04 10:29:32 companion_cube> and camlp4
2021-06-04 10:29:35 d_bot> <EduardoRFS> even the new dose3?
2021-06-04 10:29:52 d_bot> <EduardoRFS> dose3 6 changed quite a bit of stuff, even parmap they're using now
2021-06-04 10:30:34 theblatte> companion_cube: we do "dune build infer.exe"
2021-06-04 10:31:29 companion_cube> lol
2021-06-04 10:31:39 companion_cube> yeah you need the flag
2021-06-04 10:31:54 companion_cube> idk about dose3 6
2021-06-04 10:32:01 companion_cube> I stopped trying flambda a while ago
2021-06-04 10:32:17 companion_cube> using too much ram is a big problem imho
2021-06-04 10:32:45 d_bot> <EduardoRFS> that seems weird, flambda reduces the number of allocations considerably
2021-06-04 10:33:30 companion_cube> per module
2021-06-04 10:33:38 companion_cube> with this you might also gain cross module
2021-06-04 10:33:54 theblatte> ah I thought you meant too much ram used during compilation :)
2021-06-04 10:34:09 companion_cube> that's what I meant yes
2021-06-04 10:34:11 companion_cube> sorry
2021-06-04 10:34:18 companion_cube> but theblatte, try the flag :p
2021-06-04 10:34:26 d_bot> <EduardoRFS> yeah makes sense
2021-06-04 10:34:29 theblatte> companion_cube: I am!!
2021-06-04 10:34:30 companion_cube> and also, make sure .cmx are installed for all libraries
2021-06-04 10:34:52 d_bot> <EduardoRFS> do we have an idea on what leads flambda to use so much memory?
2021-06-04 10:34:57 theblatte> companion_cube: how?
2021-06-04 10:35:14 companion_cube> well most should do it if they use dune
2021-06-04 10:35:25 d_bot> <ggole> Is there any info on flambda2 floating around yet?
2021-06-04 10:35:36 companion_cube> there's zozozo's brain
2021-06-04 10:35:40 companion_cube> although it's not floating
2021-06-04 10:39:04 d_bot> <dinosaure> technically, his brain is floating in his skull
2021-06-04 10:39:15 companion_cube> he might be a robot
2021-06-04 10:39:17 companion_cube> can't be sure
2021-06-04 10:39:27 d_bot> <EduardoRFS> if he is doing flambda2 he is a robot
2021-06-04 10:40:07 zozozo> right, I can try and answer questions about flambda2
2021-06-04 10:40:17 zozozo> since I'm working on it, ^^
2021-06-04 10:41:07 companion_cube> it'll be the default if it works well enough, right?
2021-06-04 10:41:53 zozozo> that's the plan
2021-06-04 10:43:01 companion_cube> 🤞
2021-06-04 10:43:57 d_bot> <ggole> Hmm, I'm not sure I know enough about it to ask good questions
2021-06-04 10:45:07 d_bot> <ggole> Although maybe "what was not adequate about the first flambda design" is an obvious one
2021-06-04 10:45:29 theblatte> companion_cube: ah, but actually we never use dune default profiles, we do --profile=opt (or dev). There's no -opaque in the build logs
2021-06-04 10:45:41 companion_cube> ah, I see
2021-06-04 10:45:47 theblatte> phew :)
2021-06-04 10:45:49 companion_cube> (wait, there's a profile=opt??)
2021-06-04 10:46:01 theblatte> you can name your profile however you want :p
2021-06-04 10:46:40 zozozo> @ggole: basically, flambda2 now uses a CPS representation of source code, which is very useful (whereas flambda1 had an ANF representation iirc)
2021-06-04 10:46:40 theblatte> then we have (env (opt (ocamlopt_flags (:standard -O3))), etc.
2021-06-04 10:47:35 theblatte> maybe we should have -opaque for profile=dev though!
2021-06-04 10:47:52 d_bot> <EduardoRFS> wondering, when the optimization mentioned in 8958 may be triggered after inlining?
2021-06-04 10:48:19 d_bot> <EduardoRFS> It would be weird if flambda allocated two identical temporary blocks
2021-06-04 10:48:30 d_bot> <Drup> I also have a question on flambda 2.0
2021-06-04 10:48:37 d_bot> <ggole> @guigui CPS is an interesting direction. It used to be the IL style of choice, but seems to have gone right out of favour.
2021-06-04 10:49:04 zozozo> Drup: fire away, ^^
2021-06-04 10:49:07 d_bot> <Drup> Do you (the flambda team) intend to keep working on it instead of instantly decide to shoot the for moon and work on flambda 3.0 ?
2021-06-04 10:49:36 companion_cube> lolol
2021-06-04 10:49:39 companion_cube> I could say the same of ppx
2021-06-04 10:49:44 zozozo> Drup: the plan is to continue working on flambda2
2021-06-04 10:50:14 d_bot> <ggole> Although people who use ANF seem to have discovered the need for very continuation-like constructs with join points
2021-06-04 10:50:17 zozozo> basically, doing flambda1 gave the team (note that this was before I joined) some insights about how to do and not to do some things
2021-06-04 10:50:17 d_bot> <Drup> (you don't have to answer it, it's friday evening, and I know you don't really have a sway on this all that much)
2021-06-04 10:50:50 zozozo> Drup: indeed, but I'm right now in a conference call with Pierre so I can ask him, ^^
2021-06-04 10:51:02 d_bot> <Drup> Say hello from me :p
2021-06-04 10:51:22 zozozo> Drup: he says hello to you too
2021-06-04 10:52:18 theblatte> hi pchambart :)
2021-06-04 10:52:48 companion_cube> coucou to him
2021-06-04 10:52:58 d_bot> <Drup> but yeah, flambda in general is a bit moonshot infused sometimes. I understand why (it's much more fun to work on "The Perfect IR") but it's a bit infuriating.
2021-06-04 10:53:28 companion_cube> like multicore has been for a while, too
2021-06-04 10:53:31 companion_cube> or even opam 2.1
2021-06-04 10:53:36 companion_cube> seems like a common theme in OCaml :p
2021-06-04 10:53:37 theblatte> companion_cube: alright so something good still came out of that: compiling with -opaqe turns a 50s full build into a 40s one \o/ and I assume it's even better for incremental build?
2021-06-04 10:53:42 zozozo> yeah, but now with flambda2 we should have a good enough IR to do what we want and need
2021-06-04 10:54:11 companion_cube> theblatte: err it's faster builds, but slower code, yes
2021-06-04 10:54:12 d_bot> <Drup> let's hope so
2021-06-04 10:54:34 theblatte> companion_cube: it's for "dev" builds
2021-06-04 10:54:49 companion_cube> then yes
2021-06-04 10:55:07 companion_cube> with -opaque you have fully separate compilation
2021-06-04 10:55:24 theblatte> I was wondering why dune was doing so much work on incremental compilation ^^
2021-06-04 10:55:31 theblatte> thanks!
2021-06-04 10:56:35 d_bot> <Drup> (I though dune already added `-opaque` for dev builds)
2021-06-04 10:57:05 d_bot> <ggole> @guigui what was difficult before that's easy now?
2021-06-04 10:57:06 companion_cube> seems like theblatte has his own profiles
2021-06-04 10:57:37 companion_cube> zozozo: so in CPS, do you have 2 "kinds" of function calls? normal and continuations?
2021-06-04 10:57:42 companion_cube> to make sure there's no new closures?
2021-06-04 10:57:53 d_bot> <Drup> That doesn't seem very smart if those are less though-out than the normal ones :3
2021-06-04 10:57:56 theblatte> dune profiles have... weird defaults
2021-06-04 10:58:24 theblatte> fair enough :p
2021-06-04 10:59:06 zozozo> companion_cube: continuations in flambda2 are more along the lines of static jumps
2021-06-04 10:59:12 companion_cube> cool
2021-06-04 10:59:33 companion_cube> zozozo: please stop delaying the PR for ocaml.org
2021-06-04 10:59:33 companion_cube> plz
2021-06-04 11:00:48 zozozo> sorry, ^^
2021-06-04 11:00:57 companion_cube> why does a PR against a fracking website take a full week to be merged anyway
2021-06-04 11:01:29 zozozo> right, that's a problem
2021-06-04 11:02:22 companion_cube> if you want the website to go stale because no one opens a PR to update it, that's the best way to go
2021-06-04 11:02:38 octachron> companion_cube, because there is noone clearly responsible? My commit right is normally mostly for OCaml releases
2021-06-04 11:03:07 companion_cube> is Anil trying to do too many things? :p
2021-06-04 11:03:21 companion_cube> definitely not blaming you octachron
2021-06-04 11:04:36 companion_cube> just annoyed that this, which should have taken literally 5 minutes, is taking a week
2021-06-04 11:04:41 theblatte> interesting, -opaque seems to make no difference for incremental compilation, only for full compilation
2021-06-04 11:04:46 companion_cube> during which the information on the website is misleading
2021-06-04 11:05:14 companion_cube> theblatte: try modifying a file deep in the dep graph, but only the implementation, not the interface
2021-06-04 11:05:22 theblatte> that's what I tried
2021-06-04 11:05:36 companion_cube> hu
2021-06-04 11:06:25 theblatte> humm, there's a leftover -opaque in the logs, my experiment must have gone wrong, sorry, digging in further
2021-06-04 11:11:27 d_bot> <EduardoRFS> theblatte: also opaque allows to build strictly against cmi which leads to better parallelism if you're using mli well
2021-06-04 11:12:30 d_bot> <EduardoRFS> so opaque should definitely matter for incremental as without it you need to rebuilt the full tree if any module changes
2021-06-04 11:12:36 d_bot> <EduardoRFS> maybe dune doesn't have this implemented?
2021-06-04 11:12:48 d_bot> <EduardoRFS> @rgrinberg any idea here?
2021-06-04 11:13:00 theblatte> I think because we use the "dev" name for our profile -opaque was already being passed!
2021-06-04 11:13:48 theblatte> even though we override (flags ...)
2021-06-04 11:13:53 theblatte> but not ocamlopt_flags
2021-06-04 11:15:11 octachron> companion_cube, anyway my week ended 15 minutes ago, so the PR is merged.
2021-06-04 11:16:16 theblatte> and we still see a win for the full build by forcing -opaque because it passes it in a bunch of places where dune doesn't by default
2021-06-04 11:16:58 theblatte> looks like that's when building the entire libraries' .cmx
2021-06-04 11:17:21 @adrien> octachron: thanks :)
2021-06-04 11:17:46 theblatte> so, hmmm, *shrug*
2021-06-04 11:39:10 companion_cube> octachron: 😂 thank you
2021-06-04 11:43:12 companion_cube> and the website is updated already, nice
2021-06-04 11:46:07 companion_cube> "variant constructor unboxing" that's nice
2021-06-04 11:46:16 companion_cube> didn't we discuss it here recently?
2021-06-04 11:46:21 companion_cube> perhaps about bitvectors
2021-06-04 11:51:05 olle> oooooh
2021-06-04 13:58:46 zozozo> @ggole : sorry for the delay, basically, control flow manipulation is much easier in cps form, also inlining a function's body is tricky to do in ANF (and can be exponential in the worst case if you need to ensure the result if in strict ANF)
2021-06-04 13:59:23 companion_cube> coudl you post a snippet of a tiny CPS AST? :p
2021-06-04 13:59:39 companion_cube> sth where we could see let, application, and like a primitive like + ?
2021-06-04 13:59:44 zozozo> sure
2021-06-04 13:59:56 companion_cube> 👍
2021-06-04 14:00:08 companion_cube> I want to see how the continuations are represented
2021-06-04 14:07:32 zozozo> https://gist.github.com/Gbury/7a02a35cb4906914fa351183490f11b2
2021-06-04 14:07:44 zozozo> basically, a continuation is a (unique) integer
2021-06-04 14:08:05 zozozo> companion_cube: ^
2021-06-04 14:09:06 companion_cube> so, apply_cont is where you jump
2021-06-04 14:09:09 zozozo> yup
2021-06-04 14:09:29 zozozo> also, after a function call (i.e. Apply_expr), you call the given continuation with the return value of the function call
2021-06-04 14:09:35 companion_cube> and why is there 2 let?
2021-06-04 14:09:42 companion_cube> yeah
2021-06-04 14:09:49 companion_cube> and you call the function on already computed arguments
2021-06-04 14:09:59 zozozo> you can bind continuations, and regular expressions
2021-06-04 14:10:37 companion_cube> hmmm
2021-06-04 14:10:54 companion_cube> I mean, Let_expr makes sense, it's a local definition, ok
2021-06-04 14:11:00 companion_cube> but what's the "handler" in Let_cont?
2021-06-04 14:11:00 zozozo> yup
2021-06-04 14:11:07 zozozo> the code of the continuation
2021-06-04 14:11:17 companion_cube> oh shit ok
2021-06-04 14:11:17 zozozo> let_cont k args = handler in body
2021-06-04 14:11:22 companion_cube> nice
2021-06-04 14:11:43 zozozo> note that continuations are local to a function's body and cannot escape
2021-06-04 14:11:44 companion_cube> so patmatch could also create such expressions, for example
2021-06-04 14:11:55 zozozo> since continuations are not regular value (i.e. simples or named)
2021-06-04 14:11:55 companion_cube> with explicit sharing and everything
2021-06-04 14:12:02 zozozo> yes
2021-06-04 14:12:29 companion_cube> (I imagine switch could also have a default case)
2021-06-04 14:12:49 zozozo> in this case no, the switch has no default case
2021-06-04 14:12:56 zozozo> it simplifies some things
2021-06-04 14:13:07 zozozo> but in theory it could
2021-06-04 14:13:08 companion_cube> even in flambda2?
2021-06-04 14:13:17 companion_cube> I guess since you can share continuations, it's ok
2021-06-04 14:13:24 zozozo> it's just that having no default case means the code is much more regular
2021-06-04 14:13:29 zozozo> you can fold on the arms of the switch
2021-06-04 14:13:41 zozozo> and not have to specifically treat the default case
2021-06-04 14:15:30 companion_cube> heh, fair enough
2021-06-04 14:16:03 companion_cube> I think the insight that continuations are not values, is sth I didn't realize
2021-06-04 14:16:05 companion_cube> so thank you! :)
2021-06-04 14:16:27 zozozo> no problem, ^^
2021-06-04 14:30:12 d_bot> <ggole> zozozo: hmm, that's actually pretty close to what I expected. Thanks for taking the time to write it up.
2021-06-04 14:33:07 d_bot> <ggole> When I tried CPS ILs I found it difficult to perform what should be simple transformations like commuting `case` expressions, but perhaps my approach was too naive.
2021-06-04 14:37:04 zozozo> @ggole : well, commuting switches would be quite complicated indeed (and isn't done currently in flambda2)
2021-06-04 14:38:59 d_bot> <ggole> That's one benefit of a more lambda-calculus like IL, it's quite easy to do context-directed optimisations (of which commuting is probably the most significant)
2021-06-04 14:39:37 zozozo> yeah, but then again, I don't think commuting is really something that we want to do in flambda2
2021-06-04 14:39:39 d_bot> <ggole> But there are downsides with scope
2021-06-04 14:39:55 d_bot> <colin> will flambda2 carry through to faithful CPS compilation or what
2021-06-04 14:40:21 zozozo> @colin : I'm not sure what you mean ?
2021-06-04 14:41:00 d_bot> <ggole> SML/NJ style CPS all the way? Seems unlikely.
2021-06-04 14:41:03 d_bot> <colin> I've seen compilers that use CPS as an IR yet blast to something slightly different to compile to something that still uses a runtime stack
2021-06-04 14:41:22 d_bot> <colin> Yeah, I don't think SML/NJ or MLton can be described as using CPS to much of an extent nowadays tbh
2021-06-04 14:41:57 d_bot> <ggole> I thought SML/NJ still used that for their `Cont` implementation
2021-06-04 14:41:57 zozozo> ah well, the flambda IR is in CPS, but there will be no change to the other IR of the compiler, so that's that, ^^
2021-06-04 14:43:13 d_bot> <colin> is the Apply_cont constructor in this cps.ml file representing "contificated"/static continuations?
2021-06-04 14:43:43 zozozo> yeah, it represents static continuations bound previously by a Let_cont
2021-06-04 14:43:59 d_bot> <colin> interesting, I've only ever seen the IR presented in Appel's CwC book
2021-06-04 14:44:30 d_bot> <ggole> There's a nice paper on an CPS IR a bit like this that you might be interested in
2021-06-04 14:44:36 d_bot> <colin> is it by Kennedy
2021-06-04 14:44:42 d_bot> <ggole> Yeah
2021-06-04 14:44:56 d_bot> <colin> yeah, I've seen that as well actually, it's the one most people seem to go with I think
2021-06-04 14:45:17 d_bot> <ggole> Makes a lot of sense if you aren't supporting call/cc
2021-06-04 14:45:18 companion_cube> zozozo: what comes after flambda? something with a control flow graph already?
2021-06-04 14:45:36 zozozo> companion_cube: after flambda, it's cmm
2021-06-04 14:46:07 d_bot> <colin> been a while since I've toyed with CPSing compilers because very few go the full mile with the whole "no runtime stack" - they go the chicken route and use it as a GC nursery because they can't get their C compiler to do the strict (tail) call -> jumps that CPS requires and LLVM certainly can't handle CPS so you're just stuck writing your own back-end each time
2021-06-04 14:46:17 zozozo> (fun factoid: cmm quite literraly means C minus minus, :p )
2021-06-04 14:46:56 d_bot> <ggole> If the continuations are second class as in this example, then you can probably linearise to SSA fairly successfully
2021-06-04 14:47:25 companion_cube> hmm so cmm still has function calls and expressions, but no types, right?
2021-06-04 14:47:33 d_bot> <colin> I just think going from ANF -> LLVM (SSA) is simpler
2021-06-04 14:47:41 d_bot> <ggole> Although there's the usual complications of closure conversion and whatnot because LLVM is first order
2021-06-04 14:48:10 d_bot> <colin> Oleg seems to have some strong views on actually doing faithful compilation of CPS as well, along the lines of "whole-program continuations are never useful" and uh "the garbage collector doesn't like this" etc. paraphrasing (perhaps inaccurately) here
2021-06-04 14:48:21 zozozo> companion_cube: cmm has very minimal types (basically it says whether a value can/should be scanned)
2021-06-04 14:48:39 d_bot> <ggole> Well, CPS as a compiler IL is a different storly to exposing continuations reified as functions
2021-06-04 14:48:42 companion_cube> yeah, that's not typing ;)
2021-06-04 14:49:20 companion_cube> but there you eliminate continuations again, right? towards some sort of static jump, like local exceptions?
2021-06-04 14:49:27 zozozo> yup
2021-06-04 14:49:38 zozozo> cmm has static jumps and flambda continuations maps perfectly to that
2021-06-04 14:49:50 zozozo> (ofc continuations that are used exactly once can be inlined)
2021-06-04 14:50:23 companion_cube> right
2021-06-04 14:50:32 d_bot> <ggole> Either a return or a jump
2021-06-04 14:50:36 d_bot> <colin> this discussion is urging me to actually go and read Shivers' k-CFA stuff since I've always just avoided any real detail/proposed benefit of program transformations in CPS
2021-06-04 14:50:39 companion_cube> you can still use static jumps for patmathc and stuff
2021-06-04 14:50:54 d_bot> <ggole> Or maybe an exception handler if double-barrelled CPS
2021-06-04 14:51:18 zozozo> flambda actually has double-barrelled CPS
2021-06-04 14:51:22 zozozo> (flambda2)
2021-06-04 14:51:47 d_bot> <ggole> That makes sense, rather than duplicating all of the control constructs
2021-06-04 14:51:51 d_bot> <ggole> And optims on them
2021-06-04 14:52:40 d_bot> <colin> what's double-barrelled, just doing the CPS twice?
2021-06-04 14:52:58 companion_cube> wait
2021-06-04 14:53:03 companion_cube> does the second handler also work for effects?
2021-06-04 14:53:10 companion_cube> or wolud there be a third handler?
2021-06-04 14:53:11 d_bot> <ggole> Along with the usual return continuation you pass another continuation which is the error/exn path
2021-06-04 14:53:42 d_bot> <colin> ah
2021-06-04 14:54:19 zozozo> companion_cube: effects as in algebraic effects (cf multicore) ?
2021-06-04 14:54:29 companion_cube> yes
2021-06-04 14:54:34 companion_cube> runtime effects anyway
2021-06-04 14:54:38 companion_cube> the one shot continuations :)
2021-06-04 14:54:43 zozozo> that's a very good question
2021-06-04 14:55:21 companion_cube> I think exceptions will just be another effect, except in the type system, so you can probably only have 2
2021-06-04 14:55:22 d_bot> <colin> who funds OCamlPro? INRIA? Jane Street? or is it its own company
2021-06-04 14:57:27 d_bot> <Christophe> I have a question about the change log of 4.13. The change "type check x |> f and f @@ x as (f x) ` is marked as breaking change. What are the consequences of that change actually? (sorry for interrupting a very interesting conversation)
2021-06-04 14:59:15 companion_cube> it might change a few things in a subtle way
2021-06-04 14:59:22 companion_cube> like `f x` can be `f ?a ?b x`
2021-06-04 14:59:26 companion_cube> if f has optional arguments
2021-06-04 14:59:43 zozozo> @colin : OCamlPro is its own company, and janestreet is one client of ocamlpro
2021-06-04 15:00:51 d_bot> <colin> Ah, I see, I was looking at compiler jobs at Jane Street (wishful thinking) but now they don't seem like they'd be as interesting as this flambda2 stuff (unless there's some ties between both companies)
2021-06-04 15:01:19 d_bot> <Christophe> Ah yes, I didn't think of optional arguments, thanks!
2021-06-04 15:01:37 companion_cube> aren't they funding flambda2? :D
2021-06-04 15:01:37 zozozo> @colin : well, the work on flambda2 is funded by JaneStreet, ^^
2021-06-04 15:41:47 d_bot> <EduardoRFS> type check of `x |> f` as `f x` is something I was not expecting but I really appreciate
2021-06-04 15:42:00 d_bot> <EduardoRFS> now we need to type check `let x = y` in the opposite order
2021-06-04 15:43:25 d_bot> <EduardoRFS> can we implement this kind of subtyping or would it be unsound?
2021-06-04 15:43:26 d_bot> <EduardoRFS> ```ocaml
2021-06-04 15:43:27 d_bot> <EduardoRFS> module X : sig
2021-06-04 15:43:28 d_bot> <EduardoRFS> type 'a t = private 'a
2021-06-04 15:43:30 d_bot> <EduardoRFS> end = struct
2021-06-04 15:43:31 d_bot> <EduardoRFS> type 'a t = 'a
2021-06-04 15:43:32 d_bot> <EduardoRFS> end
2021-06-04 15:43:34 d_bot> <EduardoRFS> let add (a : int X.t) (b : int) = a + b
2021-06-04 15:43:35 d_bot> <EduardoRFS> ```
2021-06-04 16:03:27 d_bot> <octachron> This is already implemented, with an explicit coercion as usual: `let add a b = (a:int X.t:>int) + b`
2021-06-04 19:56:48 hackinghorn> hi
2021-06-04 19:57:03 hackinghorn> how do I run commands like ls for linux in ocaml?
2021-06-04 19:59:38 dh`> there's a binding for system() somewhere
2021-06-04 19:59:40 hackinghorn> oh, fileutils work
2021-06-04 19:59:56 hackinghorn> got it, thanks
2021-06-04 23:15:51 d_bot> <EduardoRFS> Why not implicit?
2021-06-04 23:20:48 companion_cube> There are no implicit coercions in ocaml
2021-06-04 23:51:53 d_bot> <dj charlie> 👀 nice to see the stdlib increasingly fleshed out feels good
2021-06-05 00:39:14 companion_cube> like what?
2021-06-05 00:57:05 d_bot> <dj charlie> like fold_left and fold_right with the strings
2021-06-05 00:57:12 d_bot> <dj charlie> the math functions for floats
2021-06-05 01:05:15 companion_cube> Lolol ok
2021-06-05 01:05:33 companion_cube> Fold on string, heh?
2021-06-05 01:05:43 companion_cube> Forgot that that wasn't there
2021-06-05 01:06:10 d_bot> <dj charlie> hey guy who wrote his own stdlib
2021-06-05 01:06:13 d_bot> <dj charlie> it's pretty cool to me ok?
2021-06-05 07:50:23 companion_cube> :D it is, it is
2021-06-05 09:57:02 tane> howdy! found the way
2021-06-05 11:46:29 d_bot> <giga_08> anyone familiar with ocaml verification? termination in particular
2021-06-05 12:03:08 d_bot> <darrenldl> small code or large projects?
2021-06-05 12:41:30 d_bot> <giga_08> small code
2021-06-05 13:02:29 companion_cube> @giga_08 you could give a look at try.imandra.ai (it's proprietary but termination checking is def. sth interesting)
2021-06-05 18:18:14 d_bot> <TheSkeward> learning ocaml and I occasionally giggle to myself because "O Caml! My Camel!" will pop into my head like a line from some sort of desert-themed walt whitman poem
2021-06-05 18:19:38 companion_cube> `my $camel` sounds more like perl, tbh
2021-06-05 18:21:20 d_bot> <TheSkeward> perls before swine
2021-06-05 23:22:45 kluk> how do I start using DynArray? I tried include DynArray, include Extlib, nothing works
2021-06-05 23:23:07 companion_cube> you need to have it in your dune file, if you use dune
2021-06-05 23:23:10 companion_cube> and to install it in the first place
2021-06-05 23:24:09 kluk> I don't know what dune is yet, I'm still a beginner at OCaml. how do I install DynArray? with opam right?
2021-06-05 23:24:51 companion_cube> hmmm if you're that beginner, maybe take a look at a book
2021-06-05 23:24:55 companion_cube> there's a lot to explain :/
2021-06-05 23:26:43 kluk> I just wanted to play around on the ocaml repl with some arrays... not looking for making a project, folders, dune stuff, any of that, if possible to avoid at this point. Is it possible to just play with the OCaml language to learn it and not worry about how it mixes up with unix?
2021-06-05 23:27:56 companion_cube> ah well, sure, just type `ocaml`
2021-06-05 23:28:07 companion_cube> but Dynarray is a 3rd party library for vectors/resizable arrays
2021-06-05 23:28:16 companion_cube> it's not exactly a central type in OCaml :
2021-06-05 23:28:17 companion_cube> :p
2021-06-05 23:29:14 kluk> yes I can get to the repl, but I wanted to play with arrays first without worrying about packages, does that make sense? I wanted to explore OCaml the language first, like a try.ocaml.org sort of thing if that makes sense... I wanted to have some fun with the language and learn it and not have to think about packages and managing projects for a little
2021-06-05 23:30:40 kluk> I need a stack whose elements can be randomly accessed by an integer so I just happen to have an exact use case for arrays, but I am open to suggestions
2021-06-05 23:34:07 companion_cube> arrays are in the stdlib
2021-06-05 23:34:17 companion_cube> not dynamic arrays
2021-06-05 23:34:31 companion_cube> but yeah, a stack with indexing is a good use case
2021-06-05 23:34:55 kluk> companion_cube :)
2021-06-06 00:03:27 d_bot> <Bluddy> IMO vectors should replace arrays as a primary data type in the language
2021-06-06 00:04:29 companion_cube> why "replace"?
2021-06-06 00:04:42 companion_cube> I think it'd be nice to be able to build them safely
2021-06-06 00:04:47 d_bot> <Bluddy> as the *primary* data type
2021-06-06 00:04:51 companion_cube> but otherwise, they have some overhead
2021-06-06 00:05:03 companion_cube> arrays are simpler as they're always fully initialized
2021-06-06 00:05:11 d_bot> <Bluddy> yeah the overhead is very minor though
2021-06-06 00:05:34 d_bot> <Bluddy> very few languages have arrays as their primary data structure
2021-06-06 00:05:48 d_bot> <Bluddy> python's lists are vectors
2021-06-06 00:05:49 companion_cube> I mean… java?
2021-06-06 00:06:12 companion_cube> I think the problem is the GC, because in a vector you need some unitialized space
2021-06-06 00:06:15 companion_cube> even in rust it's quite dirty
2021-06-06 00:06:46 d_bot> <Bluddy> hmm
2021-06-06 00:07:10 companion_cube> it's hard to do well without a bit of Obj currently :/
2021-06-06 00:08:53 d_bot> <Bluddy> ok so I guess python/ruby's bias may be due to their reference counting
2021-06-06 00:09:11 companion_cube> also they're insanely high level and slow :p
2021-06-06 00:09:39 d_bot> <Bluddy> yeah but that's beside the point. java has array, c# has array vs List (really a vector)
2021-06-06 00:09:54 companion_cube> java has ArrayList, but only for boxed types
2021-06-06 00:09:59 companion_cube> the primitive on the JVM is arrays, same as OCaml
2021-06-06 00:10:07 companion_cube> (except with unsound variance)
2021-06-06 00:10:12 d_bot> <Bluddy> right
2021-06-06 00:10:30 d_bot> <Bluddy> ok so yeah I think I'm just using python too much recently
2021-06-06 00:11:00 d_bot> <Bluddy> javascript also has array as its primary type
2021-06-06 00:11:07 companion_cube> remember that in OCaml, an array is *one* word of overhead
2021-06-06 00:11:12 d_bot> <Bluddy> so are python and ruby really the exceptions?
2021-06-06 00:11:26 companion_cube> as far as primitive types go? I'm not sure
2021-06-06 00:13:25 d_bot> <EduardoRFS> JS arrays are dynamic arrays / vectors
2021-06-06 00:13:34 d_bot> <EduardoRFS> and the implementation of it is really all over the place
2021-06-06 00:13:51 d_bot> <Bluddy> perl has dynamic arrays. also reference counted
2021-06-06 00:14:18 companion_cube> _scripting languages_ were primitives are all in C
2021-06-06 00:15:51 d_bot> <Bluddy> interesting. and it's gc'd.
2021-06-06 00:16:30 d_bot> <Bluddy> @companion_cube GC is only an issue if you don't have a bit to tell the GC not to scan the uninitialized memory. If OCaml had it, it wouldn't be an issue.
2021-06-06 00:16:58 companion_cube> sure, if you entirely rewrite the GC so it's not just based on the initial tag… :p
2021-06-06 00:17:13 d_bot> <EduardoRFS> but JS objects nowadays operates like OCaml blocks, adding and removing field is generally a bad idea because of the types, while it is possible that can trigger a whole lot of compiled and optimized code to be invalidated
2021-06-06 00:17:15 d_bot> <Bluddy> hmm.. no I guess you need to build it into the GC process itself so it knows how to process the vector
2021-06-06 00:17:24 d_bot> <Bluddy> so it looks at length vs capacity
2021-06-06 00:17:26 d_bot> <EduardoRFS> well we can extend the object header
2021-06-06 00:17:26 companion_cube> (well for a vector you'd need to fit 2 sizes in one, basically: capacity, and actual size)
2021-06-06 00:17:35 d_bot> <EduardoRFS> I'm looking on it during the shower
2021-06-06 00:17:52 d_bot> <Bluddy> yeah a bit is not enough, you need to teach the GC about a new kind of object
2021-06-06 00:18:00 companion_cube> also remember that vectors are 2 levels of indirection, not one
2021-06-06 00:18:06 companion_cube> one to the {len,capacity,ptr}
2021-06-06 00:18:12 companion_cube> + the pointer itself
2021-06-06 00:18:31 companion_cube> but you've got to have this level of indirection so you can change the underlying array/pointer
2021-06-06 00:19:02 d_bot> <Bluddy> that's true
2021-06-06 00:19:36 companion_cube> so that's non trivial overhead compared to a basic array, when all you need is an array
2021-06-06 00:19:53 d_bot> <EduardoRFS> but that access can be mostly reduced if you know the cell size at compile time
2021-06-06 00:19:56 d_bot> <Bluddy> the problem is that you very rarely need an array
2021-06-06 00:20:38 d_bot> <Bluddy> if your primary type is a list, all an array gives you is mutability + O(1) access to any element. it's good, but the lack of ability to extend it is annoying
2021-06-06 00:20:46 d_bot> <Bluddy> if you're doing mutable stuff, you almost always want to extend it
2021-06-06 00:20:56 companion_cube> idk, it's nice in ASTs for example
2021-06-06 00:21:03 companion_cube> I agree that often a vector is also useful
2021-06-06 00:22:19 d_bot> <EduardoRFS> I wonder if having an unrolled linked list with some tricks wouldn't be enough for almost all cases
2021-06-06 00:22:53 companion_cube> for mutable stuff we just should have a good vector
2021-06-06 00:22:59 d_bot> <EduardoRFS> like couple cells all cache aligned + pointers to additional cells if they were created all together so that you can do O(1) after a List.map
2021-06-06 00:23:03 companion_cube> for immutable stuff, we _could_ use HAMT… but well
2021-06-06 00:25:01 d_bot> <EduardoRFS> copy on write is the solution to all problems
2021-06-06 00:25:11 companion_cube> noooo :D
2021-06-06 00:27:33 d_bot> <EduardoRFS> computers are fun, nowadays you have an ALU and caching inside of the MMU
2021-06-06 00:28:05 d_bot> <EduardoRFS> lisp machine to rule them all
2021-06-06 00:51:48 d_bot> <Bluddy> companion_cube: what do you do to prevent the GC from scanning the uninitialized vector area?
2021-06-06 00:53:27 d_bot> <EduardoRFS> If it is set to 0x0 the GC should just behave normally, it's a block of tag 0, size 0
2021-06-06 00:57:50 companion_cube> @Bluddy in containers, indeed, I fill the vector with 0
2021-06-06 00:58:03 companion_cube> or 0.0 if it's a float array 🙄
2021-06-06 01:34:37 d_bot> <Bluddy> ugh yeah that's bad
2021-06-06 01:34:57 companion_cube> not like we have a better option, imhp
2021-06-06 01:34:59 companion_cube> imho
2021-06-06 01:37:39 d_bot> <Bluddy> I wonder what other languages do
2021-06-06 01:37:44 d_bot> <Bluddy> ones with GC
2021-06-06 01:40:49 companion_cube> well, java fills with null I imagine
2021-06-06 01:40:54 companion_cube> boxed primitives and all that
2021-06-06 01:41:03 companion_cube> D… probably does ugly stuff?
2021-06-06 01:41:10 companion_cube> Go has 0 values for all types, so that's easy
2021-06-06 01:41:31 companion_cube> and the scripting stuff has nil/None/whatever to fill the blanks
2021-06-06 01:42:17 d_bot> <Bluddy> at the Obj level it would be nice if you could have a contiguous array where the size is the length, and right after that you'd place a string header with the remaining size
2021-06-06 01:42:38 companion_cube> you'd have to move the header every time you push/pop? :/
2021-06-06 01:42:48 d_bot> <Bluddy> not a huge deal. same cache line
2021-06-06 01:43:07 companion_cube> ideally push should be as simple and inlineable as possible :p
2021-06-06 01:43:53 d_bot> <Bluddy> still pretty simple. copy header over, reduce string size
2021-06-06 01:44:34 companion_cube> + code path for possible resize… that's a lot more than just a normal push
2021-06-06 01:44:37 d_bot> <Bluddy> pop doesn't need to do anything because you can just zero data out at that point
2021-06-06 01:45:12 d_bot> <Bluddy> that code path is there regardless
2021-06-06 01:45:38 d_bot> <Bluddy> a multi-push function can be more efficient as it can do the header copy once
2021-06-06 01:45:59 companion_cube> pop still needs to copy the header back
2021-06-06 01:46:58 d_bot> <Bluddy> yeah I guess that's true. the only annoying thing about the header is the size counter
2021-06-06 01:47:20 companion_cube> I'd rather wish OCaml had a primitive for partially initialized arrays, and that's it
2021-06-06 01:47:22 d_bot> <Bluddy> but it should be doable with a couple of instructions
2021-06-06 01:47:43 d_bot> <Bluddy> well that's not going to happen anytime soon
2021-06-06 01:48:23 d_bot> <Bluddy> it can happen in the 64-bit runtime, but the 32-bit cannot handle it
2021-06-06 01:48:38 d_bot> <Bluddy> because you need that extra header space for the size
2021-06-06 01:48:39 companion_cube> not sure how that's related :p
2021-06-06 01:49:03 companion_cube> I just want an API for the array with a valid 0 inside
2021-06-06 01:49:16 companion_cube> that doesn't force me to Obj.magic to see if it's a float array or normal array
2021-06-06 01:49:16 d_bot> <Bluddy> valid 0?
2021-06-06 01:49:26 companion_cube> a valid object for this array
2021-06-06 01:49:42 companion_cube> a valid object for this array, _as seen by the GC_
2021-06-06 01:51:38 d_bot> <Bluddy> is this another wish? to deal more easily with float arrays? or is it related?
2021-06-06 01:51:58 companion_cube> it's related because it's the only reason I have to use Obj in containers :p
2021-06-06 01:52:04 companion_cube> (or one of the few, I can't remember)
2021-06-06 01:52:20 companion_cube> to be able to implement a vector
2021-06-06 01:52:39 d_bot> <Bluddy> but it doesn't deal with this particular issue
2021-06-06 01:52:47 d_bot> <Bluddy> I mean they're phasing out float arrays
2021-06-06 01:52:57 companion_cube> yeah that'll be nice
2021-06-06 01:53:16 companion_cube> without float arrays one could always fill the array with 0
2021-06-06 01:53:29 companion_cube> since the GC doesn't mind 0
2021-06-06 01:53:55 d_bot> <Bluddy> yeah I see that piece of code now
2021-06-06 01:54:12 d_bot> <Bluddy> let fill_with_junk_ (a:_ array) i len : unit =
2021-06-06 01:54:15 companion_cube> yep yep
2021-06-06 01:54:27 d_bot> <Bluddy> https://github.com/c-cube/ocaml-containers/blob/95e96fb5e12558fa5b1e907a8e315d8c859c23b8/src/core/CCVector.ml#L27
2021-06-06 01:54:29 companion_cube> always interested in better ideas
2021-06-06 02:04:20 d_bot> <ggole> For 64-bit machine zero (not OCaml zero) is fine for float arrays as well
2021-06-06 02:05:07 d_bot> <ggole> So you might be able to get away with coercing to `float array` and then filling with `0.0`
2021-06-06 02:05:26 d_bot> <ggole> However, the recent `FloatArray` stuff might kill that idea
2021-06-06 02:08:30 d_bot> <ggole> The no naked pointer changes might also be trouble
2021-06-06 03:32:21 d_bot> <aotmr> Hi everyone! I'm a 3rd-year CS student making personal explorations into programming languages with an emphasis on functional and concatenative languages, as well as metaprogramming and optimizing compilers.
2021-06-06 03:33:32 d_bot> <aotmr> I'm currently using OCaml to build a functional FORTH interpreter that I hope to shape into a general optimizing FORTH compiler
2021-06-06 03:33:49 d_bot> <aotmr> And right now I'm investigating to what extent I can express FORTH concepts in OCaml
2021-06-06 03:42:01 d_bot> <ggole> Hmm, they're pretty different
2021-06-06 03:43:21 d_bot> <ggole> OCaml code is very variable heavy, which seems to be at odds with the Forth philosophy of communicating between tiny bits with the stack
2021-06-06 03:43:38 d_bot> <aotmr> So, for example, inside my VM state is a list representing the current data stack.
2021-06-06 03:43:38 d_bot> <aotmr> ```ocaml
2021-06-06 03:43:40 d_bot> <aotmr> type state = {
2021-06-06 03:43:41 d_bot> <aotmr> ds : Int.t list;
2021-06-06 03:43:42 d_bot> <aotmr> (* ... *)
2021-06-06 03:43:44 d_bot> <aotmr> }
2021-06-06 03:43:45 d_bot> <aotmr> ```
2021-06-06 03:43:46 d_bot> <aotmr> Stack-based interpreters are excellent matches for programming languages with pattern matching facilities, as it turns out.
2021-06-06 03:44:15 d_bot> <aotmr> ```ocaml
2021-06-06 03:44:16 d_bot> <aotmr> type opcode =
2021-06-06 03:44:17 d_bot> <aotmr> | Lit of Int.t
2021-06-06 03:44:19 d_bot> <aotmr> | Add
2021-06-06 03:44:20 d_bot> <aotmr> | Dot
2021-06-06 03:44:21 d_bot> <aotmr> (* ... *)
2021-06-06 03:44:23 d_bot> <aotmr> ```
2021-06-06 03:44:41 d_bot> <aotmr> Let's define a small opcode set for our VM: push a literal to the stack, add the top two on the stack, and print the top on the stack (`Dot`)
2021-06-06 03:46:01 d_bot> <aotmr> Now, here's where OCaml's list matching becomes very elegant. Let's define a function, `execute`, that takes a state and an opcode and returns a new state that reflects having executed the opcode.
2021-06-06 03:46:01 d_bot> <aotmr> ```ocaml
2021-06-06 03:46:03 d_bot> <aotmr> let execute st = function
2021-06-06 03:46:04 d_bot> <aotmr> | Lit i -> { st with ds = i::st.ds }
2021-06-06 03:46:05 d_bot> <aotmr> | Add -> (* ... *)
2021-06-06 03:46:07 d_bot> <aotmr> | Dot -> (* ... *)
2021-06-06 03:46:08 d_bot> <aotmr> ```
2021-06-06 03:46:32 d_bot> <colin> awaiting the IRC users who'll ask you to read the channel description
2021-06-06 03:46:43 d_bot> <aotmr> Aw shit 🤦‍♂️
2021-06-06 03:46:49 d_bot> <colin> :p
2021-06-06 03:46:52 zozozo> @aotmr : code blocks from discord do not render great on the irc side of this channel, so it'd be best if you could use some paste website to link to code when there are more than a few lines, ^^
2021-06-06 03:46:59 d_bot> <aotmr> There it is
2021-06-06 03:47:08 zozozo> haha, XD
2021-06-06 03:47:32 d_bot> <aotmr> Well all that goes to say
2021-06-06 03:47:32 d_bot> <aotmr> You can express stack operations using pattern matching.
2021-06-06 03:48:43 d_bot> <colin> if you think that's cute, you'll like a similar idea in dependent typing where you can express stack changes (as a list) indexing the opcodes or something similar
2021-06-06 03:48:44 d_bot> <aotmr> For example, to swap the top two items on the stack, you'd use the record update syntax
2021-06-06 03:48:45 d_bot> <aotmr> `{ st with ds = match st.ds with a:🅱️:tl -> b:🅰️:tl | _ -> assert false }`
2021-06-06 03:48:46 d_bot> <aotmr> Last code block for the time being, I promise 😅
2021-06-06 03:49:17 d_bot> <aotmr> (And you can also use `let` matching, I've found, but I can't get ocaml to stop complaining even though I fully understand it'll crash if there aren't enough elements)
2021-06-06 03:49:30 d_bot> <aotmr> Oh, have a paper on that?
2021-06-06 03:49:54 d_bot> <aotmr> I'm wanting to see how high-level I can get with forth and still generate good code for small microprocessors--say, for NES and game boy dev
2021-06-06 03:50:06 d_bot> <colin> no, just thought it was very cute when I studied Agda at university, relevant construction of Hutton's razor can be found at https://github.com/fredrikNordvallForsberg/CS410-20/blob/master/Coursework/Two.agda#L492-L506 what you're saying just reminded me of it, not really relevant just in case you wanted to see cute things
2021-06-06 03:50:15 zozozo> @aotmr : small one-line blocks of code (like your last one) are mostly okay I'd say, ^^
2021-06-06 03:50:48 d_bot> <aotmr> Oh I'll look at it never the less, thanks.
2021-06-06 03:50:49 d_bot> <aotmr> Forth has its own concept of combinators and I want to try to compile those efficiently
2021-06-06 03:52:04 d_bot> <aotmr> Honestly I'd say OCaml is distantly related to FORTH just usagewise, there's a similar concept of "pipelining". Where in FORTH you'd write a series of words, passing state between them implicitly on the stack, you do the same in Ocaml when expressing a `|>` or `@@` pipeline
2021-06-06 03:54:16 d_bot> <aotmr> This is an interesting idea as, while FORTH is typically untyped, I could use this concept to track the entire lifetimes of values throughout a program
2021-06-06 03:55:20 d_bot> <colin> it's just a nice encoding of how the stack ought to change, helps the type system help you implement it correctly (though not a full specification by any means, just a cute stack requirement)
2021-06-06 03:55:27 d_bot> <ggole> There are some interesting typed concatenative langs
2021-06-06 03:55:47 d_bot> <ggole> Kitten and Cat
2021-06-06 03:55:48 d_bot> <aotmr> I've finally taken the forth-pill so to speak because I finally understand how to implement a compiler for the language
2021-06-06 03:56:18 d_bot> <colin> a whole new world.mp3 https://llvm.moe/
2021-06-06 03:56:29 d_bot> <colin> see past stack-based paradigm
2021-06-06 03:56:58 d_bot> <aotmr> Well, once I have a compiler for a stack-based VM that opens the door to using it as an intermediate representation
2021-06-06 03:57:14 d_bot> <colin> would there be any benefit
2021-06-06 03:57:27 d_bot> <colin> I, admittedly, have never seen the appeal of stack-based languages for general programming
2021-06-06 03:57:32 d_bot> <colin> I used to write postscript by hand recreationally
2021-06-06 03:57:35 d_bot> <colin> but that's about it
2021-06-06 03:57:46 d_bot> <aotmr> It's admittedly kind of recreational
2021-06-06 03:58:10 d_bot> <aotmr> I think the real strength is in the way you can build an entire system from the ground up by hand and know every moving part
2021-06-06 03:59:32 d_bot> <aotmr> You could write an optimizing compiler x86 in, oh, a month
2021-06-06 04:00:51 d_bot> <colin> sadly the majority of back-end optimisations for x86 are really just suffering
2021-06-06 04:00:59 d_bot> <aotmr> OCaml's own VM is stack-based so it's kind of circular
2021-06-06 04:01:09 d_bot> <colin> yeah but that's just the bytecode OCaml stuff
2021-06-06 04:01:12 d_bot> <aotmr> Oh yeah no x86 is a horrible architecture to program for
2021-06-06 04:01:19 d_bot> <aotmr> Sure but it's still a neat thought
2021-06-06 04:01:25 d_bot> <aotmr> But I digress
2021-06-06 04:01:28 d_bot> <colin> I used to be confused as to why Xavier Leroy's earlier work seemed to focus rather specifically on bytecode stack machines as the target of Camls
2021-06-06 04:01:51 d_bot> <colin> but then someone said like "it was research into creating a tactic computational kernel for some proof assistant"
2021-06-06 04:02:01 d_bot> <colin> not sure how true that is, perhaps someone here can clarify if that's nonsense
2021-06-06 04:02:07 d_bot> <colin> and Xavier just really likes stack machines
2021-06-06 04:02:56 d_bot> <aotmr> So, it could be that you can take advantage of immutable VM states in unit testing
2021-06-06 04:03:13 d_bot> <aotmr> And using it to accelerate the general process
2021-06-06 04:04:16 d_bot> <aotmr> If you wanted to do an exhaustive search of the program P with inputs a, b, c..., you could run P over every possible value of a, b, c
2021-06-06 04:05:19 d_bot> <aotmr> That is, we're trying to find a, b, c... that causes P to fail
2021-06-06 04:06:00 d_bot> <ggole> There's actually some tooling for that
2021-06-06 04:06:02 d_bot> <ggole> See Crowbar
2021-06-06 04:06:08 d_bot> <aotmr> One way to speed up that process is to memoize the VM state, I think
2021-06-06 04:06:44 d_bot> <ggole> It's not exhaustive search, but coverage-feedback guided random generation
2021-06-06 04:06:47 d_bot> <aotmr> If we find a "success" set of (a, b, c...), we could maybe remember all of the previous states of the VM and if we ever encounter them again we can stop early
2021-06-06 04:07:14 d_bot> <aotmr> But that would blow up your space requirements for little speedup, I'd think
2021-06-06 04:07:17 d_bot> <colin> can see why that'd help (as a form of concolic execution) but I think the accepted reality in industry is that Google fuzz their own software over billions of instances using AFL on dozens of Google cloud instances and just consider that alright
2021-06-06 04:08:00 d_bot> <aotmr> My other use case is of a rewindable debugger where you can undo all the way back to the start of the program
2021-06-06 04:08:51 d_bot> <colin> time travel debugging is pretty cool
2021-06-06 04:09:07 d_bot> <aotmr> That also brings to mind the idea of a rewindable game engine, I think rewind mechanics are pretty cool in theory
2021-06-06 04:09:12 d_bot> <colin> I always wanted a clean injection mechanism for debugging
2021-06-06 04:09:27 d_bot> <colin> hot reloading debugging stubs, that kinda thing
2021-06-06 04:09:54 d_bot> <aotmr> I'm still not entirely familiar with the mechanics of debuggers
2021-06-06 04:10:07 d_bot> <colin> syscalls and suffering™
2021-06-06 04:10:36 d_bot> <aotmr> I'm under the impression that, if you can execute from RAM, you can at least single-step on pretty much any CPU
2021-06-06 04:11:58 d_bot> <colin> yeah there's architectural single step stuff provided by most systems; *nix has PTRACE_SINGLESTEP
2021-06-06 04:12:02 d_bot> <aotmr> If you want to single-step the instruction at a given address, then you'd write some kind of "breakpoint" opcode (or, crudely, even just an absolute jump) directly following it, but you'd have to know the length of the opcode beforehand
2021-06-06 04:12:27 d_bot> <aotmr> But I'd hope consumer CPUs can single-step in silicon by now 😅
2021-06-06 04:12:28 d_bot> <colin> variable length encoding is just one part of suffering in writing x86(_64) tooling, yes
2021-06-06 04:12:42 d_bot> <aotmr> Oh yeah I guess debugging has to be infinitely easier on a fixed-length RISC
2021-06-06 04:13:14 d_bot> <aotmr> Imagine if x86 had an instruction that only decoded the length of an instruction at a given address
2021-06-06 04:13:18 d_bot> <colin> I suppose there's other challenges, given the domain where RISC microprocessors are probably most prevalently being debugged
2021-06-06 04:13:39 d_bot> <colin> who knows, they might, Intel has a ton of hidden instructions and their manual doesn't even document some of them accurately
2021-06-06 04:13:46 d_bot> <aotmr> You're right, there probably is.
2021-06-06 04:14:06 d_bot> <ggole> There's tons of hardware support for debugging
2021-06-06 04:14:09 d_bot> <colin> it's common for trampoline hooking code to come with a "variable length decoder" as a form of minimal disassembler
2021-06-06 04:14:13 d_bot> <ggole> Watch registers and that kind of thing
2021-06-06 04:14:26 d_bot> <ggole> Pretty complicated from what I understand
2021-06-06 04:14:27 d_bot> <colin> to know how many bytes to replace w/ their placed `jmp` or `push ...; ret` etc.
2021-06-06 04:16:26 d_bot> <colin> but yeah, can't lie
2021-06-06 04:16:34 d_bot> <colin> confused how we went from stack langs to all this
2021-06-06 04:16:58 d_bot> <colin> what is your ambition, aotmr, to write a forth interpreter/compiler?
2021-06-06 04:19:34 d_bot> <aotmr> Just to do it, I guess. I think it's interesting to build a software stack nearly from the bottom up--or nearly so
2021-06-06 04:19:53 d_bot> <colin> what, in Forth?
2021-06-06 04:20:04 d_bot> <aotmr> I mean, build a Forth itself from the bottom up
2021-06-06 04:20:14 d_bot> <colin> oh alright
2021-06-06 04:20:29 d_bot> <aotmr> In theory it can even be possible to replace the Ocaml parts with Forth themselves
2021-06-06 04:21:15 d_bot> <aotmr> Though "bootstrapping"
2021-06-06 04:21:47 d_bot> <aotmr> First, I'd write a forth compiler in ocaml
2021-06-06 04:22:07 d_bot> <aotmr> Then, translate the compiler to forth
2021-06-06 04:22:17 d_bot> <aotmr> Compile the compiler-in-forth with the compiler-in-ocaml
2021-06-06 04:22:30 d_bot> <aotmr> And then I have a forth compiler, compiled and written in forth
2021-06-06 04:22:36 d_bot> <colin> can graduate to something hacky like JITing the FORTH then using C FFI to map the code and somehow return opaque caml values back to the user as callables within OCaml
2021-06-06 04:22:55 d_bot> <colin> galaxy brain interplay
2021-06-06 04:23:14 d_bot> <aotmr> That sounds terrifying
2021-06-06 04:23:22 d_bot> <colin> -ly based
2021-06-06 04:23:28 d_bot> <aotmr> You got it
2021-06-06 04:23:44 d_bot> <colin> don't actually know if you can do that
2021-06-06 04:23:52 d_bot> <colin> on the conceptual level, you certainly can with enough hacks
2021-06-06 04:24:21 d_bot> <aotmr> Probably the easiest way to "JIT" stack code is just to apply peephole optimization
2021-06-06 04:24:34 d_bot> <colin> can't lie, I hate stacks
2021-06-06 04:24:56 d_bot> <aotmr> The compiler writer writes manual superwords that implement a series of smaller words in a faster way
2021-06-06 04:26:26 d_bot> <aotmr> For example, replacing `>r + r>` with the much shorter machine code for the equivalent sequence that just adds the top element of the stack to the third
2021-06-06 04:42:07 d_bot> <BobbyT> Im just marinating in all these high level ideas
2021-06-06 05:58:42 ralu> I am trying to build infer, but I keep getting error about failed dune build. So i can not build dune. Has anyone has any pointers?
2021-06-06 09:38:22 d_bot> <Bluddy> What if we make it so a proper null pointer inside an array means the end of GC scanning?
2021-06-06 10:32:24 d_bot> <Drup> @Bluddy that's not compatible with a bunch of much more interesting representations improvements (like democratizing the Zarith hack, for instance)
2021-06-06 10:52:39 d_bot> <Deadrat> Would lightweight higher kinded types be added to ocaml in the future?
2021-06-06 10:58:32 d_bot> <xvw> With modular immlicits I guess that lightweight higher kinded types will be less useful
2021-06-06 11:08:02 d_bot> <rbrott> There's a nice chapter on that idea in CPDT: <http://adam.chlipala.net/cpdt/html/Cpdt.StackMachine.html>
2021-06-06 11:08:04 d_bot> <Bluddy> @Drup could you explain the 'zarith hack'?
2021-06-06 11:09:03 d_bot> <Deadrat> But they are still years away as I understand?
2021-06-06 11:09:35 d_bot> <Drup> @Bluddy A value of type `Z.t` in zarith is either a normal ocaml integer (63bits usually, etc) or a GMP "big integers"
2021-06-06 11:11:56 d_bot> <Drup> This is achieved by considering the type morally as `int | Big of gmp`. OCaml integers already have a bit put aside for the GC to differentiate them from pointers, so we don't need an extra tag to differentiate between small integers and pointers to a big integer.
2021-06-06 11:12:15 d_bot> <Drup> This is only possible by going through the C FFI
2021-06-06 11:12:29 d_bot> <ggole> Machine zero isn't an `int` or a block though
2021-06-06 11:15:09 d_bot> <Drup> @ggole I can never remember if the tag for integers is 0 or 1.
2021-06-06 11:17:58 d_bot> <ggole> It's 1
2021-06-06 11:18:24 d_bot> <ggole> But even if it were zero, you could set aside a non-valid pointer value to indicate a truncation spot
2021-06-06 11:20:59 d_bot> <Drup> right, I'm not sure how much I like it, but it could work
2021-06-06 11:26:30 d_bot> <ggole> I guess there would have to be an `Array.unsafe_set_terminator` or something, which would be a bit nasty
2021-06-06 11:26:41 d_bot> <ggole> And I dunno what the interaction with bounds checking would be
2021-06-06 11:27:07 d_bot> <ggole> I suspect they would be more trouble than the terminator value itself though
2021-06-06 11:49:23 d_bot> <Bluddy> I need to try it out and see the performance difference.
2021-06-06 11:51:38 d_bot> <Bluddy> it's not automatically clear that setting all the memory is a bad idea
2021-06-06 13:00:48 companion_cube> I'd just like to point out that no one else uses a terminator for vectors, afaik
2021-06-06 13:00:55 companion_cube> it seems like a pretty bad idea :p
2021-06-06 13:05:10 d_bot> <ggole> Most of the other langs with vectors can handle uninitialised memory or keep the bits there without leaks
2021-06-06 13:06:34 companion_cube> and again, it's not that common
2021-06-06 13:06:57 companion_cube> languages that compile to native and have a GC and don't rely on C to implement a ton of datastructures are not plenty
2021-06-06 13:47:15 d_bot> <aotmr> I'm still not entirely used to building data structures in any language *but* C, to be honest--it feels strange
2021-06-06 13:47:52 d_bot> <aotmr> I probably just don't have practice because C is the only language that I use that doesn't have a dynamic array, really
2021-06-06 13:48:49 companion_cube> well OCaml is excellent for implementing a lot of data structures
2021-06-06 13:49:01 companion_cube> vectors just happen to be a bit on the low-level, unsafe memory thingie side
2021-06-06 13:51:37 d_bot> <aotmr> What's a good way to map from a discriminated union to successive integers?
2021-06-06 13:51:43 d_bot> <aotmr> And the other way around?
2021-06-06 13:53:53 companion_cube> ppx_deriving.enum maybe?
2021-06-06 13:54:02 companion_cube> if it's an enum, without payload on the variants, that is.
2021-06-06 13:57:14 d_bot> <aotmr> Hmm
2021-06-06 13:57:14 d_bot> <aotmr> Here's a simpler question: how do I get the "tag" of a sum type?
2021-06-06 13:57:41 companion_cube> you don't :)
2021-06-06 13:57:45 d_bot> <aotmr> I figure I can quickly map integers to most of the opcodes and then manually handle opcodes with a payload
2021-06-06 13:57:47 companion_cube> it's not really specified in the language.
2021-06-06 13:57:48 d_bot> <aotmr> Oh...
2021-06-06 13:57:52 d_bot> <octachron> The simpler and most forward-compatible way is to write the function.
2021-06-06 13:58:11 d_bot> <aotmr> True, but then I'd have to write two functions and keep them in sync manually, or generate the code.
2021-06-06 13:58:35 d_bot> <aotmr> *sigh* Okay then
2021-06-06 13:58:37 companion_cube> the function from integers to variants seems impossible to write
2021-06-06 13:58:45 companion_cube> if they have payloads that is
2021-06-06 13:58:56 d_bot> <aotmr> I'd be converting from a packed representation
2021-06-06 14:01:41 companion_cube> your best chance is codegen indeed
2021-06-06 14:01:53 companion_cube> variant to int: generate a pattern matching function
2021-06-06 14:02:10 companion_cube> int+payload to variant: well, match on the int I guess
2021-06-06 14:04:58 d_bot> <aotmr> Actually wait, I'm wrong
2021-06-06 14:04:58 d_bot> <aotmr> I shouldn't have written the VM with a discriminated union like this anyways
2021-06-06 14:05:13 d_bot> <aotmr> But, I guess I might as well keep a separate encoded and decoded form
2021-06-06 14:10:07 companion_cube> a VM seems like a good use case for C or C++ or rust, ironically
2021-06-06 14:23:33 d_bot> <aotmr> Oh it's definitely more appropriate, but I'm actually making some headway
2021-06-06 14:24:11 d_bot> <aotmr> I haven't played with ocaml in quite some time (OS issues--it didn't work well on Windows for me until quite recently)
2021-06-06 14:24:23 companion_cube> glad to hear it works better now
2021-06-06 14:24:45 d_bot> <aotmr> I mean, it works better now because it's running in WSL 😆
2021-06-06 14:25:44 d_bot> <aotmr> So I'm happy that I remember how to build list to list mappings that produce and consume varying numbers of elements
2021-06-06 15:08:24 d_bot> <aotmr> Cool, so I've figured out how to build an encoder and decoder for a variable-length instruction stream
2021-06-06 18:00:25 kluk> I get "Error: Unbound module Batteries" after doing open Batteries;; on the ocaml repl after having done opam install batteries. what am I missing?
2021-06-06 18:04:03 companion_cube> #require "batteries";;
2021-06-06 18:04:12 companion_cube> (and possibly, before that, #use "topfind";;)
2021-06-06 18:07:13 kluk> Ahhh.. it wasn't clear to me that #use was needed to bring #require but now that I ran it I can see in its blurb that it does do that. Thank you very much.
2021-06-06 18:07:49 companion_cube> also note that if you use `utop` it does the topfind thing directly
2021-06-06 18:08:03 companion_cube> you can also put the blurb in ~/.ocamlinit
2021-06-06 18:11:31 kluk> companion_cube thank you for the .ocamlinit tip
2021-06-06 18:27:10 kluk> companion_cube so now I can use DynArray from Batteries just fine :) thanks so much for the help once again.
2021-06-06 18:35:30 companion_cube> heh

View file

@ -1,17 +0,0 @@
let gen_sexp =
let open! Crowbar in
let ( >|= ) = map in
fix (fun self ->
choose
[
(([ bytes ] : _ gens) >|= fun s -> `Atom s);
([ list self ] >|= fun l -> `List l);
])
let () =
Crowbar.add_test ~name:"ccsexp_csexp_reparse" [ gen_sexp ] (fun s ->
let str = CCCanonical_sexp.to_string s in
match CCCanonical_sexp.parse_string_list str with
| Ok [ s2 ] -> assert (s = s2)
| Ok _ -> failwith "wrong number of sexps"
| Error e -> failwith e)

View file

@ -1,3 +0,0 @@
let () =
Crowbar.add_test ~name:"ccsexp_parse_string_does_not_crash" [ Crowbar.bytes ]
(fun s -> CCSexp.parse_string s |> ignore)

View file

@ -1,148 +0,0 @@
let simple_uchar_to_string (c : Uchar.t) : string =
let c = Uchar.to_int c in
let bits =
Array.make 64 false
|> Array.mapi (fun i _ -> Int.shift_right c (63 - i) land 0x1 <> 0)
in
let char_of_bit_list bits =
let bits = Array.of_list bits in
assert (Array.length bits = 8);
let res = ref 0 in
for i = 0 to 7 do
if bits.(i) then res := !res lor (0x1 lsl (7 - i))
done;
Char.chr !res
in
let get_start_from_right i = Array.get bits (63 - i) in
let chars =
if c <= 0x7F then
[
[
false;
get_start_from_right 6;
get_start_from_right 5;
get_start_from_right 4;
get_start_from_right 3;
get_start_from_right 2;
get_start_from_right 1;
get_start_from_right 0;
];
]
else if c <= 0x7FF then
[
[
true;
true;
false;
get_start_from_right 10;
get_start_from_right 9;
get_start_from_right 8;
get_start_from_right 7;
get_start_from_right 6;
];
[
true;
false;
get_start_from_right 5;
get_start_from_right 4;
get_start_from_right 3;
get_start_from_right 2;
get_start_from_right 1;
get_start_from_right 0;
];
]
else if c <= 0xFFFF then
[
[
true;
true;
true;
false;
get_start_from_right 15;
get_start_from_right 14;
get_start_from_right 13;
get_start_from_right 12;
];
[
true;
false;
get_start_from_right 11;
get_start_from_right 10;
get_start_from_right 9;
get_start_from_right 8;
get_start_from_right 7;
get_start_from_right 6;
];
[
true;
false;
get_start_from_right 5;
get_start_from_right 4;
get_start_from_right 3;
get_start_from_right 2;
get_start_from_right 1;
get_start_from_right 0;
];
]
else if c <= 0x10FFFF then
[
[
true;
true;
true;
true;
false;
get_start_from_right 20;
get_start_from_right 19;
get_start_from_right 18;
];
[
true;
false;
get_start_from_right 17;
get_start_from_right 16;
get_start_from_right 15;
get_start_from_right 14;
get_start_from_right 13;
get_start_from_right 12;
];
[
true;
false;
get_start_from_right 11;
get_start_from_right 10;
get_start_from_right 9;
get_start_from_right 8;
get_start_from_right 7;
get_start_from_right 6;
];
[
true;
false;
get_start_from_right 5;
get_start_from_right 4;
get_start_from_right 3;
get_start_from_right 2;
get_start_from_right 1;
get_start_from_right 0;
];
]
else
failwith "Unexpected case"
in
chars |> List.map char_of_bit_list |> List.to_seq |> String.of_seq
let () =
Crowbar.add_test
~name:"ccutf8_string_uchar_to_bytes_is_same_as_simple_version"
[ Crowbar.range (succ 0x10FFFF) ]
(fun c ->
Crowbar.guard (Uchar.is_valid c);
let c = Uchar.of_int c in
let simple_answer = simple_uchar_to_string c in
let answer =
let buf = ref [] in
CCUtf8_string.uchar_to_bytes c (fun c -> buf := c :: !buf);
!buf |> List.rev |> List.to_seq |> String.of_seq
in
Crowbar.check_eq simple_answer answer)

View file

@ -1,6 +0,0 @@
#!/bin/bash
script_dir=$(dirname $(readlink -f "$0"))
rm -r "$script_dir"/../fuzz-*-input
rm -r "$script_dir"/../fuzz-*-output

View file

@ -1,9 +0,0 @@
(executables
(flags
(-w "+a-4-9-29-37-40-42-44-48-50-32-70" -g))
(names
ccsexp_parse_string_does_not_crash
ccutf8_string_uchar_to_bytes_is_same_as_simple_version
ccsexp_csexp_reparse)
(optional)
(libraries crowbar containers))

View file

@ -1,15 +0,0 @@
#!/bin/bash
script_dir=$(dirname $(readlink -f "$0"))
echo "Building"
dune build @all
echo ""
echo "Fuzzing tests available:"
for file in "$script_dir"/../_build/default/fuzz/*.exe; do
echo "- "$(basename $file | sed 's/\.exe$//')
done

View file

@ -1,37 +0,0 @@
#!/bin/bash
script_dir=$(dirname $(readlink -f "$0"))
skip_build=$2
if [[ "$skip_build" != "skip_build" ]]; then
echo "Building"
dune build @all
fi
if [[ "$1" == "" ]]; then
echo "Please enter a fuzzing test to run"
exit 1
fi
name=$(echo "$1" | sed 's/\.exe$//' | sed 's/\.ml$//')
echo "Creating input directory"
input_dir="$script_dir"/../"fuzz-""$name""-input"
output_dir="$script_dir"/../"fuzz-""$name""-output"
mkdir -p "$input_dir"
echo "abcd" > "$input_dir"/dummy
if [ -d "$output_dir" ]; then
afl-fuzz -t 1000 -i - -o "$output_dir" "$script_dir"/../_build/default/fuzz/"$name".exe @@
else
mkdir -p "$output_dir"
afl-fuzz -t 1000 -i "$input_dir" -o "$output_dir" "$script_dir"/../_build/default/fuzz/"$name".exe @@
fi

View file

@ -1,126 +0,0 @@
#!/bin/bash
cpu_count=$(grep -c ^processor /proc/cpuinfo)
simul_test_count=$[cpu_count-1]
test_timeout="10m"
script_dir=$(dirname $(readlink -f "$0"))
log_dir="$script_dir"/../fuzz-logs
echo "Building"
dune build @all
echo ""
start_date=$(date "+%Y-%m-%d %H:%M")
start_time=$(date "+%s")
names=()
i=0
for file in "$script_dir"/../_build/default/fuzz/*.exe; do
name=$(basename $file | sed 's/\.exe$//')
names[$i]=$name
i=$[i+1]
done
test_count=${#names[@]}
echo "Fuzzing tests available:"
for name in ${names[@]}; do
echo "- "$name
done
echo ""
echo "Fuzzing start time:" $start_date
echo ""
echo "Starting $test_count tests"
echo ""
mkdir -p "$log_dir"
i=0
while (( $i < $test_count )); do
if (( $test_count - $i >= $simul_test_count )); then
tests_to_run=$simul_test_count
else
tests_to_run=$[test_count - i]
fi
echo "Running $tests_to_run tests in parallel"
for (( c=0; c < $tests_to_run; c++ )); do
name=${names[$i]}
if [[ "$name" != "" ]]; then
echo " Starting $name"
(AFL_NO_UI=1 timeout "$test_timeout" "$script_dir"/run.sh "$name" skip_build > "$log_dir"/"$name".log) &
i=$[i+1]
fi
done
echo "Waiting for $test_timeout"
sleep $test_timeout
echo "Terminating tests"
pkill afl-fuzz
sleep 5
echo ""
echo "$[test_count - i] / $test_count tests remaining"
echo ""
done
end_date=$(date "+%Y-%m-%d %H:%M")
end_time=$(date "+%s")
echo ""
echo "Test end:" $end_date
echo ""
echo "Time elapsed:" $[(end_time - start_time) / 60] "minutes"
test_fail_count=0
tests_failed=()
for name in ${names[@]}; do
output_dir="$script_dir"/../"fuzz-""$name""-output"
crashes_dir="$output_dir"/crashes
if [ -z "$(ls -A $crashes_dir)" ]; then
# crashes dir is empty
:
else
# crashes dir is not empty
test_fail_count=$[$test_fail_count + 1]
tests_failed+=("$name")
fi
done
echo "========================================"
if [[ $test_fail_count == 0 ]]; then
echo "All $test_count tests passed"
exit_code=0
else
echo "$test_fail_count tests failed"
echo ""
echo "List of tests failed :"
for t in ${tests_failed[@]}; do
echo " "$t
done
exit_code=1
fi

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: bc144ef7b5b54947fab9662a822f9179)
Containers_lwt
# OASIS_STOP

4
lwt/containers_lwt.mllib Normal file
View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: bc144ef7b5b54947fab9662a822f9179)
Containers_lwt
# OASIS_STOP

View file

@ -0,0 +1,6 @@
# OASIS_START
# DO NOT EDIT (digest: 0522ffe492b9796ab336d55b925afe68)
Behavior
Lwt_automaton
Lwt_actor
# OASIS_STOP

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: 2df0608accd158542ebcb00720cfe599)
Containers_misc
# OASIS_STOP

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: 2df0608accd158542ebcb00720cfe599)
Containers_misc
# OASIS_STOP

View file

@ -0,0 +1,34 @@
# OASIS_START
# DO NOT EDIT (digest: 5f2c8615af923cd3ff229b6d10c55bc8)
FHashtbl
FlatHashtbl
Hashset
Heap
LazyGraph
PersistentGraph
PHashtbl
SkipList
SplayTree
SplayMap
Univ
Bij
PiCalculus
RAL
UnionFind
SmallSet
AbsSet
CSM
TTree
PrintBox
HGraph
Automaton
Conv
Bidir
Iteratee
BTree
Ty
Cause
AVL
ParseReact
Mixtbl
# OASIS_STOP

938
myocamlbuild.ml Normal file
View file

@ -0,0 +1,938 @@
(* OASIS_START *)
(* DO NOT EDIT (digest: 9ebeddeee0d56b1f8c98544fabcbbd9b) *)
module OASISGettext = struct
(* # 22 "src/oasis/OASISGettext.ml" *)
let ns_ str = str
let s_ str = str
let f_ (str: ('a, 'b, 'c, 'd) format4) = str
let fn_ fmt1 fmt2 n =
if n = 1 then
fmt1^^""
else
fmt2^^""
let init = []
end
module OASISString = struct
(* # 22 "src/oasis/OASISString.ml" *)
(** Various string utilities.
Mostly inspired by extlib and batteries ExtString and BatString libraries.
@author Sylvain Le Gall
*)
let nsplitf str f =
if str = "" then
[]
else
let buf = Buffer.create 13 in
let lst = ref [] in
let push () =
lst := Buffer.contents buf :: !lst;
Buffer.clear buf
in
let str_len = String.length str in
for i = 0 to str_len - 1 do
if f str.[i] then
push ()
else
Buffer.add_char buf str.[i]
done;
push ();
List.rev !lst
(** [nsplit c s] Split the string [s] at char [c]. It doesn't include the
separator.
*)
let nsplit str c =
nsplitf str ((=) c)
let find ~what ?(offset=0) str =
let what_idx = ref 0 in
let str_idx = ref offset in
while !str_idx < String.length str &&
!what_idx < String.length what do
if str.[!str_idx] = what.[!what_idx] then
incr what_idx
else
what_idx := 0;
incr str_idx
done;
if !what_idx <> String.length what then
raise Not_found
else
!str_idx - !what_idx
let sub_start str len =
let str_len = String.length str in
if len >= str_len then
""
else
String.sub str len (str_len - len)
let sub_end ?(offset=0) str len =
let str_len = String.length str in
if len >= str_len then
""
else
String.sub str 0 (str_len - len)
let starts_with ~what ?(offset=0) str =
let what_idx = ref 0 in
let str_idx = ref offset in
let ok = ref true in
while !ok &&
!str_idx < String.length str &&
!what_idx < String.length what do
if str.[!str_idx] = what.[!what_idx] then
incr what_idx
else
ok := false;
incr str_idx
done;
!what_idx = String.length what
let strip_starts_with ~what str =
if starts_with ~what str then
sub_start str (String.length what)
else
raise Not_found
let ends_with ~what ?(offset=0) str =
let what_idx = ref ((String.length what) - 1) in
let str_idx = ref ((String.length str) - 1) in
let ok = ref true in
while !ok &&
offset <= !str_idx &&
0 <= !what_idx do
if str.[!str_idx] = what.[!what_idx] then
decr what_idx
else
ok := false;
decr str_idx
done;
!what_idx = -1
let strip_ends_with ~what str =
if ends_with ~what str then
sub_end str (String.length what)
else
raise Not_found
let replace_chars f s =
let buf = Buffer.create (String.length s) in
String.iter (fun c -> Buffer.add_char buf (f c)) s;
Buffer.contents buf
let lowercase_ascii =
replace_chars
(fun c ->
if (c >= 'A' && c <= 'Z') then
Char.chr (Char.code c + 32)
else
c)
let uncapitalize_ascii s =
if s <> "" then
(lowercase_ascii (String.sub s 0 1)) ^ (String.sub s 1 ((String.length s) - 1))
else
s
let uppercase_ascii =
replace_chars
(fun c ->
if (c >= 'a' && c <= 'z') then
Char.chr (Char.code c - 32)
else
c)
let capitalize_ascii s =
if s <> "" then
(uppercase_ascii (String.sub s 0 1)) ^ (String.sub s 1 ((String.length s) - 1))
else
s
end
module OASISUtils = struct
(* # 22 "src/oasis/OASISUtils.ml" *)
open OASISGettext
module MapExt =
struct
module type S =
sig
include Map.S
val add_list: 'a t -> (key * 'a) list -> 'a t
val of_list: (key * 'a) list -> 'a t
val to_list: 'a t -> (key * 'a) list
end
module Make (Ord: Map.OrderedType) =
struct
include Map.Make(Ord)
let rec add_list t =
function
| (k, v) :: tl -> add_list (add k v t) tl
| [] -> t
let of_list lst = add_list empty lst
let to_list t = fold (fun k v acc -> (k, v) :: acc) t []
end
end
module MapString = MapExt.Make(String)
module SetExt =
struct
module type S =
sig
include Set.S
val add_list: t -> elt list -> t
val of_list: elt list -> t
val to_list: t -> elt list
end
module Make (Ord: Set.OrderedType) =
struct
include Set.Make(Ord)
let rec add_list t =
function
| e :: tl -> add_list (add e t) tl
| [] -> t
let of_list lst = add_list empty lst
let to_list = elements
end
end
module SetString = SetExt.Make(String)
let compare_csl s1 s2 =
String.compare (OASISString.lowercase_ascii s1) (OASISString.lowercase_ascii s2)
module HashStringCsl =
Hashtbl.Make
(struct
type t = string
let equal s1 s2 = (compare_csl s1 s2) = 0
let hash s = Hashtbl.hash (OASISString.lowercase_ascii s)
end)
module SetStringCsl =
SetExt.Make
(struct
type t = string
let compare = compare_csl
end)
let varname_of_string ?(hyphen='_') s =
if String.length s = 0 then
begin
invalid_arg "varname_of_string"
end
else
begin
let buf =
OASISString.replace_chars
(fun c ->
if ('a' <= c && c <= 'z')
||
('A' <= c && c <= 'Z')
||
('0' <= c && c <= '9') then
c
else
hyphen)
s;
in
let buf =
(* Start with a _ if digit *)
if '0' <= s.[0] && s.[0] <= '9' then
"_"^buf
else
buf
in
OASISString.lowercase_ascii buf
end
let varname_concat ?(hyphen='_') p s =
let what = String.make 1 hyphen in
let p =
try
OASISString.strip_ends_with ~what p
with Not_found ->
p
in
let s =
try
OASISString.strip_starts_with ~what s
with Not_found ->
s
in
p^what^s
let is_varname str =
str = varname_of_string str
let failwithf fmt = Printf.ksprintf failwith fmt
let rec file_location ?pos1 ?pos2 ?lexbuf () =
match pos1, pos2, lexbuf with
| Some p, None, _ | None, Some p, _ ->
file_location ~pos1:p ~pos2:p ?lexbuf ()
| Some p1, Some p2, _ ->
let open Lexing in
let fn, lineno = p1.pos_fname, p1.pos_lnum in
let c1 = p1.pos_cnum - p1.pos_bol in
let c2 = c1 + (p2.pos_cnum - p1.pos_cnum) in
Printf.sprintf (f_ "file %S, line %d, characters %d-%d") fn lineno c1 c2
| _, _, Some lexbuf ->
file_location
~pos1:(Lexing.lexeme_start_p lexbuf)
~pos2:(Lexing.lexeme_end_p lexbuf)
()
| None, None, None ->
s_ "<position undefined>"
let failwithpf ?pos1 ?pos2 ?lexbuf fmt =
let loc = file_location ?pos1 ?pos2 ?lexbuf () in
Printf.ksprintf (fun s -> failwith (Printf.sprintf "%s: %s" loc s)) fmt
end
module OASISExpr = struct
(* # 22 "src/oasis/OASISExpr.ml" *)
open OASISGettext
open OASISUtils
type test = string
type flag = string
type t =
| EBool of bool
| ENot of t
| EAnd of t * t
| EOr of t * t
| EFlag of flag
| ETest of test * string
type 'a choices = (t * 'a) list
let eval var_get t =
let rec eval' =
function
| EBool b ->
b
| ENot e ->
not (eval' e)
| EAnd (e1, e2) ->
(eval' e1) && (eval' e2)
| EOr (e1, e2) ->
(eval' e1) || (eval' e2)
| EFlag nm ->
let v =
var_get nm
in
assert(v = "true" || v = "false");
(v = "true")
| ETest (nm, vl) ->
let v =
var_get nm
in
(v = vl)
in
eval' t
let choose ?printer ?name var_get lst =
let rec choose_aux =
function
| (cond, vl) :: tl ->
if eval var_get cond then
vl
else
choose_aux tl
| [] ->
let str_lst =
if lst = [] then
s_ "<empty>"
else
String.concat
(s_ ", ")
(List.map
(fun (cond, vl) ->
match printer with
| Some p -> p vl
| None -> s_ "<no printer>")
lst)
in
match name with
| Some nm ->
failwith
(Printf.sprintf
(f_ "No result for the choice list '%s': %s")
nm str_lst)
| None ->
failwith
(Printf.sprintf
(f_ "No result for a choice list: %s")
str_lst)
in
choose_aux (List.rev lst)
end
# 437 "myocamlbuild.ml"
module BaseEnvLight = struct
(* # 22 "src/base/BaseEnvLight.ml" *)
module MapString = Map.Make(String)
type t = string MapString.t
let default_filename = Filename.concat (Sys.getcwd ()) "setup.data"
let load ?(allow_empty=false) ?(filename=default_filename) ?stream () =
let line = ref 1 in
let lexer st =
let st_line =
Stream.from
(fun _ ->
try
match Stream.next st with
| '\n' -> incr line; Some '\n'
| c -> Some c
with Stream.Failure -> None)
in
Genlex.make_lexer ["="] st_line
in
let rec read_file lxr mp =
match Stream.npeek 3 lxr with
| [Genlex.Ident nm; Genlex.Kwd "="; Genlex.String value] ->
Stream.junk lxr; Stream.junk lxr; Stream.junk lxr;
read_file lxr (MapString.add nm value mp)
| [] -> mp
| _ ->
failwith
(Printf.sprintf "Malformed data file '%s' line %d" filename !line)
in
match stream with
| Some st -> read_file (lexer st) MapString.empty
| None ->
if Sys.file_exists filename then begin
let chn = open_in_bin filename in
let st = Stream.of_channel chn in
try
let mp = read_file (lexer st) MapString.empty in
close_in chn; mp
with e ->
close_in chn; raise e
end else if allow_empty then begin
MapString.empty
end else begin
failwith
(Printf.sprintf
"Unable to load environment, the file '%s' doesn't exist."
filename)
end
let rec var_expand str env =
let buff = Buffer.create ((String.length str) * 2) in
Buffer.add_substitute
buff
(fun var ->
try
var_expand (MapString.find var env) env
with Not_found ->
failwith
(Printf.sprintf
"No variable %s defined when trying to expand %S."
var
str))
str;
Buffer.contents buff
let var_get name env = var_expand (MapString.find name env) env
let var_choose lst env = OASISExpr.choose (fun nm -> var_get nm env) lst
end
# 517 "myocamlbuild.ml"
module MyOCamlbuildFindlib = struct
(* # 22 "src/plugins/ocamlbuild/MyOCamlbuildFindlib.ml" *)
(** OCamlbuild extension, copied from
* https://ocaml.org/learn/tutorials/ocamlbuild/Using_ocamlfind_with_ocamlbuild.html
* by N. Pouillard and others
*
* Updated on 2016-06-02
*
* Modified by Sylvain Le Gall
*)
open Ocamlbuild_plugin
type conf = {no_automatic_syntax: bool}
let run_and_read = Ocamlbuild_pack.My_unix.run_and_read
let blank_sep_strings = Ocamlbuild_pack.Lexers.blank_sep_strings
let exec_from_conf exec =
let exec =
let env = BaseEnvLight.load ~allow_empty:true () in
try
BaseEnvLight.var_get exec env
with Not_found ->
Printf.eprintf "W: Cannot get variable %s\n" exec;
exec
in
let fix_win32 str =
if Sys.os_type = "Win32" then begin
let buff = Buffer.create (String.length str) in
(* Adapt for windowsi, ocamlbuild + win32 has a hard time to handle '\\'.
*)
String.iter
(fun c -> Buffer.add_char buff (if c = '\\' then '/' else c))
str;
Buffer.contents buff
end else begin
str
end
in
fix_win32 exec
let split s ch =
let buf = Buffer.create 13 in
let x = ref [] in
let flush () =
x := (Buffer.contents buf) :: !x;
Buffer.clear buf
in
String.iter
(fun c ->
if c = ch then
flush ()
else
Buffer.add_char buf c)
s;
flush ();
List.rev !x
let split_nl s = split s '\n'
let before_space s =
try
String.before s (String.index s ' ')
with Not_found -> s
(* ocamlfind command *)
let ocamlfind x = S[Sh (exec_from_conf "ocamlfind"); x]
(* This lists all supported packages. *)
let find_packages () =
List.map before_space (split_nl & run_and_read (exec_from_conf "ocamlfind" ^ " list"))
(* Mock to list available syntaxes. *)
let find_syntaxes () = ["camlp4o"; "camlp4r"]
let well_known_syntax = [
"camlp4.quotations.o";
"camlp4.quotations.r";
"camlp4.exceptiontracer";
"camlp4.extend";
"camlp4.foldgenerator";
"camlp4.listcomprehension";
"camlp4.locationstripper";
"camlp4.macro";
"camlp4.mapgenerator";
"camlp4.metagenerator";
"camlp4.profiler";
"camlp4.tracer"
]
let dispatch conf =
function
| After_options ->
(* By using Before_options one let command line options have an higher
* priority on the contrary using After_options will guarantee to have
* the higher priority override default commands by ocamlfind ones *)
Options.ocamlc := ocamlfind & A"ocamlc";
Options.ocamlopt := ocamlfind & A"ocamlopt";
Options.ocamldep := ocamlfind & A"ocamldep";
Options.ocamldoc := ocamlfind & A"ocamldoc";
Options.ocamlmktop := ocamlfind & A"ocamlmktop";
Options.ocamlmklib := ocamlfind & A"ocamlmklib"
| After_rules ->
(* Avoid warnings for unused tag *)
flag ["tests"] N;
(* When one link an OCaml library/binary/package, one should use
* -linkpkg *)
flag ["ocaml"; "link"; "program"] & A"-linkpkg";
(* For each ocamlfind package one inject the -package option when
* compiling, computing dependencies, generating documentation and
* linking. *)
List.iter
begin fun pkg ->
let base_args = [A"-package"; A pkg] in
(* TODO: consider how to really choose camlp4o or camlp4r. *)
let syn_args = [A"-syntax"; A "camlp4o"] in
let (args, pargs) =
(* Heuristic to identify syntax extensions: whether they end in
".syntax"; some might not.
*)
if not (conf.no_automatic_syntax) &&
(Filename.check_suffix pkg "syntax" ||
List.mem pkg well_known_syntax) then
(syn_args @ base_args, syn_args)
else
(base_args, [])
in
flag ["ocaml"; "compile"; "pkg_"^pkg] & S args;
flag ["ocaml"; "ocamldep"; "pkg_"^pkg] & S args;
flag ["ocaml"; "doc"; "pkg_"^pkg] & S args;
flag ["ocaml"; "link"; "pkg_"^pkg] & S base_args;
flag ["ocaml"; "infer_interface"; "pkg_"^pkg] & S args;
(* TODO: Check if this is allowed for OCaml < 3.12.1 *)
flag ["ocaml"; "compile"; "package("^pkg^")"] & S pargs;
flag ["ocaml"; "ocamldep"; "package("^pkg^")"] & S pargs;
flag ["ocaml"; "doc"; "package("^pkg^")"] & S pargs;
flag ["ocaml"; "infer_interface"; "package("^pkg^")"] & S pargs;
end
(find_packages ());
(* Like -package but for extensions syntax. Morover -syntax is useless
* when linking. *)
List.iter begin fun syntax ->
flag ["ocaml"; "compile"; "syntax_"^syntax] & S[A"-syntax"; A syntax];
flag ["ocaml"; "ocamldep"; "syntax_"^syntax] & S[A"-syntax"; A syntax];
flag ["ocaml"; "doc"; "syntax_"^syntax] & S[A"-syntax"; A syntax];
flag ["ocaml"; "infer_interface"; "syntax_"^syntax] &
S[A"-syntax"; A syntax];
end (find_syntaxes ());
(* The default "thread" tag is not compatible with ocamlfind.
* Indeed, the default rules add the "threads.cma" or "threads.cmxa"
* options when using this tag. When using the "-linkpkg" option with
* ocamlfind, this module will then be added twice on the command line.
*
* To solve this, one approach is to add the "-thread" option when using
* the "threads" package using the previous plugin.
*)
flag ["ocaml"; "pkg_threads"; "compile"] (S[A "-thread"]);
flag ["ocaml"; "pkg_threads"; "doc"] (S[A "-I"; A "+threads"]);
flag ["ocaml"; "pkg_threads"; "link"] (S[A "-thread"]);
flag ["ocaml"; "pkg_threads"; "infer_interface"] (S[A "-thread"]);
flag ["c"; "pkg_threads"; "compile"] (S[A "-thread"]);
flag ["ocaml"; "package(threads)"; "compile"] (S[A "-thread"]);
flag ["ocaml"; "package(threads)"; "doc"] (S[A "-I"; A "+threads"]);
flag ["ocaml"; "package(threads)"; "link"] (S[A "-thread"]);
flag ["ocaml"; "package(threads)"; "infer_interface"] (S[A "-thread"]);
flag ["c"; "package(threads)"; "compile"] (S[A "-thread"]);
| _ ->
()
end
module MyOCamlbuildBase = struct
(* # 22 "src/plugins/ocamlbuild/MyOCamlbuildBase.ml" *)
(** Base functions for writing myocamlbuild.ml
@author Sylvain Le Gall
*)
open Ocamlbuild_plugin
module OC = Ocamlbuild_pack.Ocaml_compiler
type dir = string
type file = string
type name = string
type tag = string
type t =
{
lib_ocaml: (name * dir list * string list) list;
lib_c: (name * dir * file list) list;
flags: (tag list * (spec OASISExpr.choices)) list;
(* Replace the 'dir: include' from _tags by a precise interdepends in
* directory.
*)
includes: (dir * dir list) list;
}
(* # 110 "src/plugins/ocamlbuild/MyOCamlbuildBase.ml" *)
let env_filename = Pathname.basename BaseEnvLight.default_filename
let dispatch_combine lst =
fun e ->
List.iter
(fun dispatch -> dispatch e)
lst
let tag_libstubs nm =
"use_lib"^nm^"_stubs"
let nm_libstubs nm =
nm^"_stubs"
let dispatch t e =
let env = BaseEnvLight.load ~allow_empty:true () in
match e with
| Before_options ->
let no_trailing_dot s =
if String.length s >= 1 && s.[0] = '.' then
String.sub s 1 ((String.length s) - 1)
else
s
in
List.iter
(fun (opt, var) ->
try
opt := no_trailing_dot (BaseEnvLight.var_get var env)
with Not_found ->
Printf.eprintf "W: Cannot get variable %s\n" var)
[
Options.ext_obj, "ext_obj";
Options.ext_lib, "ext_lib";
Options.ext_dll, "ext_dll";
]
| After_rules ->
(* Declare OCaml libraries *)
List.iter
(function
| nm, [], intf_modules ->
ocaml_lib nm;
let cmis =
List.map (fun m -> (OASISString.uncapitalize_ascii m) ^ ".cmi")
intf_modules in
dep ["ocaml"; "link"; "library"; "file:"^nm^".cma"] cmis
| nm, dir :: tl, intf_modules ->
ocaml_lib ~dir:dir (dir^"/"^nm);
List.iter
(fun dir ->
List.iter
(fun str ->
flag ["ocaml"; "use_"^nm; str] (S[A"-I"; P dir]))
["compile"; "infer_interface"; "doc"])
tl;
let cmis =
List.map (fun m -> dir^"/"^(OASISString.uncapitalize_ascii m)^".cmi")
intf_modules in
dep ["ocaml"; "link"; "library"; "file:"^dir^"/"^nm^".cma"]
cmis)
t.lib_ocaml;
(* Declare directories dependencies, replace "include" in _tags. *)
List.iter
(fun (dir, include_dirs) ->
Pathname.define_context dir include_dirs)
t.includes;
(* Declare C libraries *)
List.iter
(fun (lib, dir, headers) ->
(* Handle C part of library *)
flag ["link"; "library"; "ocaml"; "byte"; tag_libstubs lib]
(S[A"-dllib"; A("-l"^(nm_libstubs lib)); A"-cclib";
A("-l"^(nm_libstubs lib))]);
flag ["link"; "library"; "ocaml"; "native"; tag_libstubs lib]
(S[A"-cclib"; A("-l"^(nm_libstubs lib))]);
if bool_of_string (BaseEnvLight.var_get "native_dynlink" env) then
flag ["link"; "program"; "ocaml"; "byte"; tag_libstubs lib]
(S[A"-dllib"; A("dll"^(nm_libstubs lib))]);
(* When ocaml link something that use the C library, then one
need that file to be up to date.
This holds both for programs and for libraries.
*)
dep ["link"; "ocaml"; tag_libstubs lib]
[dir/"lib"^(nm_libstubs lib)^"."^(!Options.ext_lib)];
dep ["compile"; "ocaml"; tag_libstubs lib]
[dir/"lib"^(nm_libstubs lib)^"."^(!Options.ext_lib)];
(* TODO: be more specific about what depends on headers *)
(* Depends on .h files *)
dep ["compile"; "c"]
headers;
(* Setup search path for lib *)
flag ["link"; "ocaml"; "use_"^lib]
(S[A"-I"; P(dir)]);
)
t.lib_c;
(* Add flags *)
List.iter
(fun (tags, cond_specs) ->
let spec = BaseEnvLight.var_choose cond_specs env in
let rec eval_specs =
function
| S lst -> S (List.map eval_specs lst)
| A str -> A (BaseEnvLight.var_expand str env)
| spec -> spec
in
flag tags & (eval_specs spec))
t.flags
| _ ->
()
let dispatch_default conf t =
dispatch_combine
[
dispatch t;
MyOCamlbuildFindlib.dispatch conf;
]
end
# 878 "myocamlbuild.ml"
open Ocamlbuild_plugin;;
let package_default =
{
MyOCamlbuildBase.lib_ocaml =
[
("containers", ["src/core"], []);
("containers_unix", ["src/unix"], []);
("containers_sexp", ["src/sexp"], []);
("containers_data", ["src/data"], []);
("containers_iter", ["src/iter"], []);
("containers_thread", ["src/threads"], []);
("containers_top", ["src/top"], [])
];
lib_c = [];
flags = [];
includes =
[
("src/top",
["src/core"; "src/data"; "src/iter"; "src/sexp"; "src/unix"]);
("src/threads", ["src/core"]);
("qtest",
[
"src/core";
"src/data";
"src/iter";
"src/sexp";
"src/threads";
"src/unix"
]);
("examples", ["src/sexp"]);
("benchs", ["src/core"; "src/data"; "src/iter"; "src/threads"])
]
}
;;
let conf = {MyOCamlbuildFindlib.no_automatic_syntax = false}
let dispatch_default = MyOCamlbuildBase.dispatch_default conf package_default;;
# 919 "myocamlbuild.ml"
(* OASIS_STOP *)
let doc_intro = "doc/intro.txt" ;;
Ocamlbuild_plugin.dispatch dispatch_default;;
dispatch
(MyOCamlbuildBase.dispatch_combine [
begin function
| After_rules ->
(* Documentation index *)
dep ["ocaml"; "doc"; "extension:html"] & [doc_intro] ;
flag ["ocaml"; "doc"; "extension:html"]
& S[A"-t"; A"Containers doc"; A"-intro"; P doc_intro ];
| _ -> ()
end;
dispatch_default
])

49
opam Normal file
View file

@ -0,0 +1,49 @@
opam-version: "1.2"
name: "containers"
version: "1.5"
author: "Simon Cruanes"
maintainer: "simon.cruanes@inria.fr"
build: [
["./configure"
"--prefix" prefix
"--disable-bench"
"--disable-tests"
"--%{base-unix:enable}%-unix"
"--enable-docs"
]
[make "build"]
]
install: [
[make "install"]
]
build-doc: [ make "doc" ]
build-test: [ make "test" ]
remove: [
["ocamlfind" "remove" "containers"]
]
depends: [
"ocamlfind" {build}
"base-bytes"
"result"
"ocamlbuild" {build}
]
depopts: [
"base-unix"
"base-threads"
"qtest" { test }
]
conflicts: [
"sequence" { < "0.5" }
]
tags: [ "stdlib" "containers" "iterators" "list" "heap" "queue" ]
homepage: "https://github.com/c-cube/ocaml-containers/"
doc: "http://cedeela.fr/~simon/software/containers/"
available: [ocaml-version >= "4.01.0"]
dev-repo: "https://github.com/c-cube/ocaml-containers.git"
bug-reports: "https://github.com/c-cube/ocaml-containers/issues/"
post-messages: [
"Small release with many bugfixes and a few new functions.
A summary hub.com/c-cube/ocaml-containers/issues/84
changelog: https://github.com/c-cube/ocaml-containers/blob/1.3/CHANGELOG.adoc"
]

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: ea286cccf88f4c81c7b4627216807d4e)
CCPervasives
# OASIS_STOP

View file

@ -0,0 +1,4 @@
# OASIS_START
# DO NOT EDIT (digest: ea286cccf88f4c81c7b4627216807d4e)
CCPervasives
# OASIS_STOP

View file

@ -1,4 +0,0 @@
#!/bin/sh
OPTS="--profile=release --display=quiet"
exec dune exec $OPTS -- benchs/run_benchs_hash.exe $@

View file

@ -1,4 +0,0 @@
#!/bin/sh
OPTS="--profile=release --display=quiet"
exec dune exec $OPTS -- benchs/run_benchs.exe $@

8915
setup.ml Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
# OASIS_START
# DO NOT EDIT (digest: b0f5a3a0b7428f165d73d9e621998219)
Containers_advanced
CCLinq
CCBatch
CCCat
CCMonadIO
# OASIS_STOP

View file

@ -0,0 +1,8 @@
# OASIS_START
# DO NOT EDIT (digest: b0f5a3a0b7428f165d73d9e621998219)
Containers_advanced
CCLinq
CCBatch
CCCat
CCMonadIO
# OASIS_STOP

View file

@ -1,183 +0,0 @@
module Str_map = Map.Make (String)
type t =
| Int of int64
| String of string
| List of t list
| Map of t Str_map.t
let rec equal t1 t2 =
match t1, t2 with
| Int i1, Int i2 -> i1 = i2
| String s1, String s2 -> s1 = s2
| List l1, List l2 ->
(try List.for_all2 equal l1 l2 with Invalid_argument _ -> false)
| Map d1, Map d2 -> Str_map.equal equal d1 d2
| (Int _ | String _ | List _ | Map _), _ -> false
let rec hash t =
let module H = CCHash in
match t with
| Int i -> H.int64 i
| String s -> H.combine2 10 (H.string s)
| List l -> H.combine2 20 (H.list hash l)
| Map l ->
H.combine2 30
( H.iter (H.pair H.string hash) @@ fun k ->
Str_map.iter (fun x y -> k (x, y)) l )
let int64 i : t = Int i
let int i : t = int64 (Int64.of_int i)
let string s : t = String s
let list l : t = List l
let map m : t = Map m
let map_of_list l : t =
map @@ List.fold_left (fun m (k, v) -> Str_map.add k v m) Str_map.empty l
let rec pp_debug out (self : t) : unit =
let fpf = Format.fprintf in
match self with
| Int i -> fpf out "%Ld" i
| String s -> fpf out "%S" s
| List l ->
fpf out "[@[<hv>";
List.iteri
(fun i v ->
if i > 0 then fpf out ";@ ";
pp_debug out v)
l;
fpf out "@]]"
| Map m ->
fpf out "{@[<hv>";
let i = ref 0 in
Str_map.iter
(fun k v ->
if !i > 0 then fpf out ";@ ";
incr i;
fpf out "@[<1>%S:@ %a@]" k pp_debug v)
m;
fpf out "@]}"
let to_string_debug self = Format.asprintf "%a" pp_debug self
module Encode = struct
let bpf = Printf.bprintf
let fpf = Printf.fprintf
let rec to_buffer (buf : Buffer.t) (self : t) : unit =
let recurse = to_buffer buf in
let addc = Buffer.add_char in
match self with
| Int i -> bpf buf "i%Lde" i
| String s -> bpf buf "%d:%s" (String.length s) s
| List l ->
addc buf 'l';
List.iter recurse l;
addc buf 'e'
| Map l ->
addc buf 'd';
Str_map.iter
(fun k v -> bpf buf "%d:%s%a" (String.length k) k to_buffer v)
l;
addc buf 'e'
let to_string (self : t) : string =
let buf = Buffer.create 32 in
to_buffer buf self;
Buffer.contents buf
let rec to_chan (oc : out_channel) (self : t) : unit =
let recurse = to_chan oc in
let addc = output_char in
match self with
| Int i -> fpf oc "i%Lde" i
| String s -> fpf oc "%d:%s" (String.length s) s
| List l ->
addc oc 'l';
List.iter recurse l;
addc oc 'e'
| Map l ->
addc oc 'd';
Str_map.iter (fun k v -> fpf oc "%d:%s%a" (String.length k) k to_chan v) l;
addc oc 'e'
let to_fmt out self = Format.pp_print_string out (to_string self)
end
module Decode = struct
exception Fail
let of_string s =
let i = ref 0 in
let[@inline] check_not_eof () =
if !i >= String.length s then raise_notrace Fail
in
let rec top () : t =
check_not_eof ();
match String.unsafe_get s !i with
| 'l' ->
incr i;
read_list []
| 'd' ->
incr i;
read_map Str_map.empty
| 'i' ->
incr i;
let n = read_int 'e' true 0 in
int n
| '0' .. '9' -> String (parse_str_len ())
| _ -> raise_notrace Fail
(* read integer until char [stop] is met, consume [stop], return int *)
and read_int stop sign n : int =
check_not_eof ();
match String.unsafe_get s !i with
| c when c == stop ->
incr i;
if sign then
n
else
-n
| '-' when stop == 'e' && sign && n = 0 ->
incr i;
read_int stop false n
| '0' .. '9' as c ->
incr i;
read_int stop sign (Char.code c - Char.code '0' + (10 * n))
| _ -> raise_notrace Fail
and parse_str_len () : string =
let n = read_int ':' true 0 in
if !i + n > String.length s then raise_notrace Fail;
let s = String.sub s !i n in
i := !i + n;
s
and read_list acc =
check_not_eof ();
match String.unsafe_get s !i with
| 'e' ->
incr i;
List (List.rev acc)
| _ ->
let x = top () in
read_list (x :: acc)
and read_map acc =
check_not_eof ();
match String.unsafe_get s !i with
| 'e' ->
incr i;
Map acc
| _ ->
let k = parse_str_len () in
let v = top () in
read_map (Str_map.add k v acc)
in
try Some (top ()) with Fail -> None
let of_string_exn s =
match of_string s with
| Some x -> x
| None -> failwith "bencode.decode: invalid string"
end

View file

@ -1,44 +0,0 @@
(** Basic Bencode decoder/encoder.
See https://en.wikipedia.org/wiki/Bencode .
@since 3.8 *)
module Str_map : module type of Map.Make (String)
type t =
| Int of int64
| String of string
| List of t list
| Map of t Str_map.t
val equal : t -> t -> bool
val hash : t -> int
val pp_debug : Format.formatter -> t -> unit
(** Printer for diagnostic/human consumption *)
val to_string_debug : t -> string
val int : int -> t
val int64 : int64 -> t
val string : string -> t
val list : t list -> t
val map_of_list : (string * t) list -> t
val map : t Str_map.t -> t
(** Encoding *)
module Encode : sig
val to_string : t -> string
val to_buffer : Buffer.t -> t -> unit
val to_chan : out_channel -> t -> unit
val to_fmt : Format.formatter -> t -> unit
end
(** Decoding *)
module Decode : sig
val of_string : string -> t option
val of_string_exn : string -> t
(** Parse string.
@raise Failure if the string is not valid bencode. *)
end

View file

@ -1,5 +0,0 @@
(library
(name containers_bencode)
(public_name containers.bencode)
(libraries containers)
(synopsis "Bencode codec for containers (the format for bittorrent files)"))

View file

@ -0,0 +1,5 @@
# OASIS_START
# DO NOT EDIT (digest: 4901abd33a2dfcf115ddeffb93e1186e)
CCBigstring
CCArray1
# OASIS_STOP

View file

@ -0,0 +1,5 @@
# OASIS_START
# DO NOT EDIT (digest: 4901abd33a2dfcf115ddeffb93e1186e)
CCBigstring
CCArray1
# OASIS_STOP

View file

@ -1,323 +0,0 @@
module Fmt = CCFormat
type t =
[ `Null
| `Undefined
| `Simple of int
| `Bool of bool
| `Int of int64
| `Float of float
| `Bytes of string
| `Text of string
| `Array of t list
| `Map of (t * t) list
| `Tag of int * t
]
let rec pp_diagnostic out (self : t) =
match self with
| `Null -> Fmt.string out "null"
| `Undefined -> Fmt.string out "undefined"
| `Simple i -> Fmt.fprintf out "simple(%d)" i
| `Bool b -> Fmt.bool out b
| `Int i -> Fmt.int64 out i
| `Float f -> Fmt.float out f
| `Bytes b -> Fmt.fprintf out "h'%s'" (CCString.to_hex b)
| `Text s -> Fmt.fprintf out "%S" s
| `Array l ->
Fmt.fprintf out "[@[";
List.iteri
(fun i x ->
if i > 0 then Fmt.fprintf out ",@ ";
pp_diagnostic out x)
l;
Fmt.fprintf out "@]]"
| `Map l ->
Fmt.fprintf out "{@[";
List.iteri
(fun i (k, v) ->
if i > 0 then Fmt.fprintf out ",@ ";
Fmt.fprintf out "@[%a:@ %a@]" pp_diagnostic k pp_diagnostic v)
l;
Fmt.fprintf out "@]}"
| `Tag (i, x) -> Fmt.fprintf out "%d(@[%a@])" i pp_diagnostic x
let to_string_diagnostic (self : t) : string =
Format.asprintf "@[<h>%a@]" pp_diagnostic self
exception Indefinite
let[@inline] i64_to_int i =
let j = Int64.to_int i in
if Int64.(of_int j = i) then
j
else
failwith "int64 does not fit in int"
let decode_exn (s : string) : t =
let b = Bytes.unsafe_of_string s in
let i = ref 0 in
(* currently at end delimiter? *)
let[@inline] is_break_stop_code () = Char.code s.[!i] = 0b111_11111 in
let[@inline] read_i8 () =
let c = Char.code s.[!i] in
incr i;
c
in
let[@inline] read_i16 () =
let c = Bytes.get_uint16_be b !i in
i := !i + 2;
c
in
let[@inline] read_i32 () =
let c = Bytes.get_int32_be b !i in
i := !i + 4;
c
in
let[@inline] read_i64 () =
let c = Bytes.get_int64_be b !i in
i := !i + 8;
c
in
let reserve_n n =
let j = !i in
if j + n > String.length s then failwith "cbor: cannot extract slice";
i := !i + n;
j
in
(* read integer value from least significant bits *)
let read_int ~allow_indefinite low =
match low with
| _ when low < 0 -> failwith "cbor: invalid length"
| _ when low < 24 -> Int64.of_int low
| 24 -> Int64.of_int (read_i8 ())
| 25 -> Int64.of_int (read_i16 ())
| 26 -> Int64.of_int32 (read_i32 ())
| 27 -> read_i64 ()
| 28 | 29 | 30 -> failwith "cbor: invalid length"
| 31 ->
if allow_indefinite then
raise_notrace Indefinite
else
failwith "cbor: invalid integer 31 in this context"
| _ -> assert false
in
(* appendix D
double decode_half(unsigned char *halfp) {
unsigned half = (halfp[0] << 8) + halfp[1];
unsigned exp = (half >> 10) & 0x1f;
unsigned mant = half & 0x3ff;
double val;
if (exp == 0) val = ldexp(mant, -24);
else if (exp != 31) val = ldexp(mant + 1024, exp - 25);
else val = mant == 0 ? INFINITY : NAN;
return half & 0x8000 ? -val : val;
}
*)
let decode_f16 (half : int) : float =
(* exponent is bits 15:10 *)
let exp = (half lsr 10) land 0x1f in
(* mantissa is bits 9:0 *)
let mant = half land 0x3ff in
let value =
if exp = 0 then
ldexp (float mant) (-24)
else if exp <> 31 then
ldexp (float (mant + 1024)) (exp - 25)
else if mant = 0 then
infinity
else
nan
in
if half land 0x8000 <> 0 then
-.value
else
value
in
(* roughly follow https://www.rfc-editor.org/rfc/rfc8949.html#pseudocode *)
let rec read_value () =
let c = read_i8 () in
let high = (c land 0b111_00000) lsr 5 in
let low = c land 0b000_11111 in
match high with
| 0 -> `Int (read_int ~allow_indefinite:false low)
| 1 ->
let i = read_int ~allow_indefinite:false low in
`Int Int64.(sub minus_one i)
| 2 ->
let s = read_bytes ~ty:`Bytes low in
`Bytes s
| 3 ->
let s = read_bytes ~ty:`String low in
`Text s
| 4 ->
let l =
match read_int ~allow_indefinite:true low |> i64_to_int with
| len -> List.init len (fun _ -> read_value ())
| exception Indefinite ->
let l = ref [] in
while not (is_break_stop_code ()) do
l := read_value () :: !l
done;
incr i;
(* consume stop code *)
List.rev !l
in
`Array l
| 5 ->
let l =
match read_int ~allow_indefinite:true low |> i64_to_int with
| len -> List.init len (fun _ -> read_pair ())
| exception Indefinite ->
let l = ref [] in
while not (is_break_stop_code ()) do
l := read_pair () :: !l
done;
incr i;
(* consume stop code *)
List.rev !l
in
`Map l
| 6 ->
let tag = read_int ~allow_indefinite:false low |> i64_to_int in
let v = read_value () in
`Tag (tag, v)
| 7 ->
(* simple or float,
https://www.rfc-editor.org/rfc/rfc8949.html#fpnocont *)
let i = read_int ~allow_indefinite:false low in
(match low with
| 20 -> `Bool false
| 21 -> `Bool true
| 22 -> `Null
| 23 -> `Undefined
| _ when low <= 24 -> `Simple (i64_to_int i)
| 25 ->
(* float16 *)
`Float (decode_f16 (Int64.to_int i))
| 26 ->
(* float 32 *)
`Float (Int32.float_of_bits (Int64.to_int32 i))
| 27 ->
(* float 64 *)
`Float (Int64.float_of_bits i)
| 28 | 29 | 30 -> failwith "cbor: malformed"
| 31 -> failwith "uncaught 'break' stop code"
| _ -> assert false (* unreachable *))
| _ ->
(* unreachable *)
assert false
and read_bytes ~ty low =
match read_int ~allow_indefinite:true low |> i64_to_int with
| exception Indefinite ->
let buf = Buffer.create 32 in
while not (is_break_stop_code ()) do
match read_value (), ty with
| `Text s, `String | `Bytes s, `Bytes -> Buffer.add_string buf s
| _ -> failwith "cbor: invalid chunk in indefinite length string/byte"
done;
incr i;
(* consume stop code *)
Buffer.contents buf
| len ->
let off = reserve_n len in
String.sub s off len
and read_pair () =
let k = read_value () in
let v = read_value () in
k, v
in
read_value ()
let decode s = try Ok (decode_exn s) with Failure s -> Error s
let encode ?(buf = Buffer.create 32) (self : t) : string =
Buffer.clear buf;
let[@inline] add_byte (high : int) (low : int) =
let i = (high lsl 5) lor low in
assert (i land 0xff == i);
Buffer.add_char buf (Char.unsafe_chr i)
in
let add_i64 (i : int64) = Buffer.add_int64_be buf i in
(* add unsigned integer, including first tag byte *)
let add_uint (high : int) (x : int64) =
assert (x >= 0L);
if x < 24L then
add_byte high (i64_to_int x)
else if x <= 0xffL then (
add_byte high 24;
Buffer.add_char buf (Char.unsafe_chr (i64_to_int x))
) else if x <= 0xff_ffL then (
add_byte high 25;
Buffer.add_uint16_be buf (i64_to_int x)
) else if x <= 0xff_ff_ff_ffL then (
add_byte high 26;
Buffer.add_int32_be buf (Int64.to_int32 x)
) else (
add_byte high 27;
Buffer.add_int64_be buf x
)
in
let rec encode_val (self : t) : unit =
match self with
| `Bool false -> add_byte 7 20
| `Bool true -> add_byte 7 21
| `Null -> add_byte 7 22
| `Undefined -> add_byte 7 23
| `Simple i ->
if i < 24 then
add_byte 7 i
else if i <= 0xff then (
add_byte 7 24;
Buffer.add_char buf (Char.unsafe_chr i)
) else
failwith "cbor: simple value too high (above 255)"
| `Float f ->
add_byte 7 27;
(* float 64 *)
add_i64 (Int64.bits_of_float f)
| `Array l ->
add_uint 4 (Int64.of_int (List.length l));
List.iter encode_val l
| `Map l ->
add_uint 5 (Int64.of_int (List.length l));
List.iter
(fun (k, v) ->
encode_val k;
encode_val v)
l
| `Text s ->
add_uint 3 (Int64.of_int (String.length s));
Buffer.add_string buf s
| `Bytes s ->
add_uint 2 (Int64.of_int (String.length s));
Buffer.add_string buf s
| `Tag (t, v) ->
add_uint 6 (Int64.of_int t);
encode_val v
| `Int i ->
if i >= Int64.zero then
add_uint 0 i
else if Int64.(add min_int 2L) > i then (
(* large negative int, be careful. encode [(-i)-1] via int64. *)
add_byte 1 27;
Buffer.add_int64_be buf Int64.(neg (add 1L i))
) else
add_uint 1 Int64.(sub (neg i) one)
in
encode_val self;
Buffer.contents buf

View file

@ -1,32 +0,0 @@
(** CBOR encoder/decoder.
The type is chosen to be compatible with ocaml-cbor.
See {{: https://www.rfc-editor.org/rfc/rfc8949.html} the RFC}.
{b note} this is experimental.
@since 3.9
*)
type t =
[ `Null
| `Undefined
| `Simple of int
| `Bool of bool
| `Int of int64
| `Float of float
| `Bytes of string
| `Text of string
| `Array of t list
| `Map of (t * t) list
| `Tag of int * t
]
val pp_diagnostic : t CCFormat.printer
val to_string_diagnostic : t -> string
val encode : ?buf:Buffer.t -> t -> string
val decode : string -> (t, string) result
val decode_exn : string -> t
(** Like {!decode}.
@raise Failure if the string isn't valid *)

View file

@ -1,7 +0,0 @@
(library
(name containers_cbor)
(libraries containers)
(preprocess
(action
(run %{project_root}/src/core/cpp/cpp.exe %{input-file})))
(public_name containers.cbor))

File diff suppressed because it is too large Load diff

View file

@ -1,143 +0,0 @@
(** {1 Code generators} *)
module Fmt = CCFormat
let spf = Printf.sprintf
let fpf = Fmt.fprintf
type code =
| Base of { pp: unit Fmt.printer }
| Struct of string * code list
| Sig of string * code list
module Code = struct
type t = code
let in_struct m (cs : t list) : t = Struct (m, cs)
let in_sig m (cs : t list) : t = Sig (m, cs)
let rec pp_rec out c =
let ppl = Fmt.(list ~sep:(return "@ ") pp_rec) in
match c with
| Base { pp } -> pp out ()
| Struct (m, cs) ->
fpf out "@[<hv2>module %s = struct@ %a@;<1 -2>end@]" m ppl cs
| Sig (m, cs) -> fpf out "@[<hv2>module %s : sig@ %a@;<1 -2>end@]" m ppl cs
let pp out c = fpf out "@[<v>%a@]" pp_rec c
let to_string c = Fmt.to_string pp c
let mk_pp pp = Base { pp }
let mk_str s = Base { pp = Fmt.const Fmt.string s }
end
module Bitfield = struct
type field = {
f_name: string;
f_offset: int;
f_def: field_def;
}
and field_def =
| F_bit
| F_int of { width: int }
type t = {
name: string;
mutable fields: field list;
mutable width: int;
emit_failure_if_too_wide: bool;
}
let make ?(emit_failure_if_too_wide = true) ~name () : t =
{ name; fields = []; width = 0; emit_failure_if_too_wide }
let total_width self = self.width
let field_bit self f_name =
let f_offset = total_width self in
let f = { f_name; f_offset; f_def = F_bit } in
self.fields <- f :: self.fields;
self.width <- 1 + self.width
let field_int self ~width f_name : unit =
let f_offset = total_width self in
let f = { f_name; f_offset; f_def = F_int { width } } in
self.fields <- f :: self.fields;
self.width <- self.width + width
let empty_name self =
if self.name = "t" then
"empty"
else
spf "empty_%s" self.name
let gen_ml self : code =
Code.mk_pp @@ fun out () ->
fpf out "@[<v>type %s = int@," self.name;
fpf out "@[let %s : %s = 0@]@," (empty_name self) self.name;
List.iter
(fun f ->
let inline = "[@inline]" in
(* TODO: option to enable/disable that *)
let off = f.f_offset in
match f.f_def with
| F_bit ->
let x_lsr =
if off = 0 then
"x"
else
spf "(x lsr %d)" off
in
fpf out "@[let%s get_%s (x:%s) : bool = (%s land 1) <> 0@]@," inline
f.f_name self.name x_lsr;
let mask_shifted = 1 lsl off in
fpf out
"@[<2>let%s set_%s (v:bool) (x:%s) : %s =@ if v then x lor %d else \
x land (lnot %d)@]@,"
inline f.f_name self.name self.name mask_shifted mask_shifted
| F_int { width } ->
let mask0 = (1 lsl width) - 1 in
fpf out "@[let%s get_%s (x:%s) : int = ((x lsr %d) land %d)@]@,"
inline f.f_name self.name off mask0;
fpf out
"@[<2>let%s set_%s (i:int) (x:%s) : %s =@ assert ((i land %d) == \
i);@ ((x land (lnot %d)) lor (i lsl %d))@]@,"
inline f.f_name self.name self.name mask0 (mask0 lsl off) off)
(List.rev self.fields);
(* check width *)
if self.emit_failure_if_too_wide then
fpf out
"(* check that int size is big enough *)@,\
@[let () = assert (Sys.int_size >= %d);;@]"
(total_width self);
fpf out "@]"
let gen_mli self : code =
Code.mk_pp @@ fun out () ->
fpf out "@[<v>type %s = private int@," self.name;
fpf out "@[<v>val %s : %s@," (empty_name self) self.name;
List.iter
(fun f ->
match f.f_def with
| F_bit ->
fpf out "@[val get_%s : %s -> bool@]@," f.f_name self.name;
fpf out "@[val set_%s : bool -> %s -> %s@]@," f.f_name self.name
self.name
| F_int { width } ->
fpf out "@[val get_%s : %s -> int@]@," f.f_name self.name;
fpf out
"@,@[(** %d bits integer *)@]@,@[val set_%s : int -> %s -> %s@]@,"
width f.f_name self.name self.name)
(List.rev self.fields);
fpf out "@]"
end
let emit_chan oc cs =
let fmt = Fmt.formatter_of_out_channel oc in
List.iter (fun c -> Fmt.fprintf fmt "@[%a@]@." Code.pp c) cs;
Fmt.fprintf fmt "@?"
let emit_file file cs = CCIO.with_out file (fun oc -> emit_chan oc cs)
let emit_string cs : string =
Fmt.asprintf "@[<v>%a@]" (Fmt.list ~sep:(Fmt.return "@ ") Code.pp) cs

View file

@ -1,86 +0,0 @@
(** {1 Code generators}
The code generator library is designed to be used from a build system
(for example, from [dune]) to generate efficient code for features
that are harder to provide at runtime.
The idea is that the build system should invoke some OCaml script
that depends on [containers.codegen]; the script uses the DSL below
to describe what code to generate (e.g. a description of a bitfield type)
and emits a [.ml] file (and possibly a [.mli] file).
For example, the build script might contain:
{[
module CG = Containers_codegen
let () =
let module B = CG.Bitfield in
let b = B.make ~name:"t" () in
B.field_bit b "x";
B.field_bit b "y";
B.field_bit b "z";
B.field_int b ~width:5 "foo";
CG.emit_file "foo.mli" [B.gen_mli b];
CG.emit_file "foo.ml" [B.gen_ml b];
()
]}
and this will produce [foo.ml] and [foo.mli] with a bitfield containing
[x], [y], and [z].
*)
module Fmt = CCFormat
type code
(** {2 Representation of OCaml code} *)
module Code : sig
type t = code
val pp : t Fmt.printer
val to_string : t -> string
val mk_pp : unit Fmt.printer -> t
val mk_str : string -> t
val in_struct : string -> t list -> t
val in_sig : string -> t list -> t
end
(** {2 Generate efficient bitfields that fit in an integer} *)
module Bitfield : sig
type t
val make : ?emit_failure_if_too_wide:bool -> name:string -> unit -> t
(** Make a new bitfield with the given name.
@param name the name of the generated type
@param emit_failure_if_too_wide if true, generated code includes a runtime
assertion that {!Sys.int_size} is wide enough to support this type *)
val field_bit : t -> string -> unit
(** [field_bit ty name] adds a field of size [1] to the bitfield [ty],
with name [name]. The generate code will provide get/set for
a boolean. *)
val field_int : t -> width:int -> string -> unit
(** [field_int ty name ~width] adds a field of size [width] to
the bitfield with name [name].
The accessors will be for integers of [width] bits, and the
setter might assert that the provided integer fits. *)
val total_width : t -> int
(** Total width in bits of the given bitfield. *)
val gen_mli : t -> code
(** Generate code for the type signature for the given bitfield *)
val gen_ml : t -> code
(** Generate code for the implementation for the given bitfield *)
end
val emit_file : string -> code list -> unit
(** [emit_file file cs] emits code fragments [cs] into the given file
at path [file] *)
val emit_chan : out_channel -> code list -> unit
val emit_string : code list -> string

View file

@ -1,6 +0,0 @@
(library
(name containers_codegen)
(public_name containers.codegen)
(synopsis "code generators for Containers")
(libraries containers)
(flags :standard -warn-error -a+8))

View file

@ -1,25 +0,0 @@
; emit tests
(executable
(name emit_tests)
(modules emit_tests)
(flags :standard -warn-error -a+8)
(libraries containers containers.codegen))
(rule
(targets test_bitfield.ml test_bitfield.mli)
(action
(run ./emit_tests.exe)))
; run tests
(executables
(names test_bitfield)
(modules test_bitfield)
(flags :standard -warn-error -a+8)
(libraries containers))
(rule
(alias runtest)
(action
(run ./test_bitfield.exe)))

View file

@ -1,58 +0,0 @@
module CG = Containers_codegen
module Vec = CCVector
let spf = Printf.sprintf
let emit_bitfields () =
let module B = CG.Bitfield in
let ml = Vec.create () in
let mli = Vec.create () in
(let b = B.make ~name:"t" () in
B.field_bit b "x";
B.field_bit b "y";
B.field_bit b "z";
B.field_int b ~width:5 "foo";
Vec.push ml (CG.Code.in_struct "T1" [ B.gen_ml b ]);
Vec.push mli (CG.Code.in_sig "T1" [ B.gen_mli b ]);
(* check width *)
Vec.push ml
(CG.Code.mk_str (spf "let() = assert (%d = 8);;" (B.total_width b)));
());
Vec.push ml
@@ CG.Code.mk_str
{|
let n_fails = ref 0;;
at_exit (fun () -> if !n_fails > 0 then exit 1);;
let assert_true line s =
if not s then ( incr n_fails; Printf.eprintf "test failure at %d\n%!" line);;
|};
let test1 =
{|
assert_true __LINE__ T1.(get_y (empty |> set_x true |> set_y true |> set_foo 10));;
assert_true __LINE__ T1.(get_x (empty |> set_x true |> set_y true |> set_foo 10));;
assert_true __LINE__ T1.(get_y (empty |> set_x true |> set_z true
|> set_y false |> set_x false |> set_y true));;
assert_true __LINE__ T1.(get_z (empty |> set_z true));;
assert_true __LINE__ T1.(not @@ get_x (empty |> set_z true));;
assert_true __LINE__ T1.(not @@ get_y (empty |> set_z true |> set_x true));;
assert_true __LINE__ T1.(not @@ get_y (empty |> set_z true |> set_foo 18));;
(* check width of foo *)
assert_true __LINE__ T1.(try ignore (empty |> set_foo (1 lsl 6)); false with _ -> true);;
assert_true __LINE__ T1.(12 = get_foo (empty |> set_x true |> set_foo 12 |> set_x false));;
assert_true __LINE__ T1.(24 = get_foo (empty |> set_y true |> set_foo 24 |> set_z true));;
|}
|> CG.Code.mk_str
in
Vec.push ml test1;
CG.emit_file "test_bitfield.ml" (Vec.to_list ml);
CG.emit_file "test_bitfield.mli" (Vec.to_list mli);
()
let () =
emit_bitfields ();
()

View file

@ -1,430 +1,528 @@
(* This file is free software, part of containers. See file "license" for more details. *) (* This file is free software, part of containers. See file "license" for more details. *)
(** {1 Array utils} *) (** {1 Array utils} *)
type 'a iter = ('a -> unit) -> unit type 'a sequence = ('a -> unit) -> unit
type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]
type 'a gen = unit -> 'a option type 'a gen = unit -> 'a option
type 'a equal = 'a -> 'a -> bool type 'a equal = 'a -> 'a -> bool
type 'a ord = 'a -> 'a -> int type 'a ord = 'a -> 'a -> int
type 'a random_gen = Random.State.t -> 'a type 'a random_gen = Random.State.t -> 'a
type 'a printer = Format.formatter -> 'a -> unit type 'a printer = Format.formatter -> 'a -> unit
(*$T
let st = Random.State.make [||] in let a = 0--10000 in \
let b = Array.copy a in shuffle_with st a; a <> b
*)
(** {2 Arrays} *) (** {2 Arrays} *)
include Array include Array
let empty = [||] type 'a t = 'a array
let empty = [| |]
let map = Array.map
let map2 f a b =
if Array.length a <> Array.length b then invalid_arg "map2";
Array.init (Array.length a) (fun i -> f (Array.unsafe_get a i) (Array.unsafe_get b i))
let length = Array.length
let get = Array.get
let get_safe a i = let get_safe a i =
if i >= 0 && i < Array.length a then if i>=0 && i<Array.length a
Some (Array.unsafe_get a i) then Some (Array.unsafe_get a i)
else else None
None
(*$=
(Some 1) (get_safe [|1;2;3|] 0)
(Some 2) (get_safe [|1;2;3|] 1)
(Some 3) (get_safe [|1;2;3|] 2)
None (get_safe [|1;2;3|] 4)
None (get_safe [|1;2;3|] max_int)
None (get_safe [|1;2;3|] ~-1)
None (get_safe [|1;2;3|] ~-42)
*)
let set = Array.set
let map_inplace f a = Array.iteri (fun i e -> Array.unsafe_set a i (f e)) a
let mapi_inplace f a = Array.iteri (fun i e -> Array.unsafe_set a i (f i e)) a
let fold = Array.fold_left let fold = Array.fold_left
let foldi f acc a = let foldi f acc a =
let rec aux acc i = let rec aux acc i =
if i = Array.length a then if i = Array.length a then acc else aux (f acc i a.(i)) (i+1)
acc
else
aux (f acc i a.(i)) (i + 1)
in in
aux acc 0 aux acc 0
let fold_while f acc a = let fold_while f acc a =
let rec fold_while_i f acc i = let rec fold_while_i f acc i =
if i < Array.length a then ( if i < Array.length a then
let acc, cont = f acc a.(i) in let acc, cont = f acc a.(i) in
match cont with match cont with
| `Stop -> acc | `Stop -> acc
| `Continue -> fold_while_i f acc (i + 1) | `Continue -> fold_while_i f acc (i+1)
) else else acc
acc in fold_while_i f acc 0
in
fold_while_i f acc 0 (*$T
fold_while (fun acc b -> if b then acc+1, `Continue else acc, `Stop) 0 (Array.of_list [true;true;false;true]) = 2
*)
let fold_map f acc a = let fold_map f acc a =
let n = length a in let n = length a in
(* need special case for initializing the result *) (* need special case for initializing the result *)
if n = 0 then if n = 0 then acc, [||]
acc, [||]
else ( else (
let acc, b0 = f acc a.(0) in let acc, b0 = f acc a.(0) in
let res = Array.make n b0 in let res = Array.make n b0 in
let acc = ref acc in let acc = ref acc in
for i = 1 to n - 1 do for i = 1 to n-1 do
let new_acc, b = f !acc a.(i) in let new_acc, b = f !acc a.(i) in
acc := new_acc; acc := new_acc;
res.(i) <- b res.(i) <- b;
done; done;
!acc, res !acc, res
) )
(*$=
(6, [|"1"; "2"; "3"|]) \
(fold_map (fun acc x->acc+x, string_of_int x) 0 [|1;2;3|])
*)
(*$Q
Q.(array int) (fun a -> \
fold_map (fun acc x -> x::acc, x) [] a = (List.rev @@ Array.to_list a, a))
*)
let scan_left f acc a = let scan_left f acc a =
let n = length a in let n = length a in
let res = Array.make (n + 1) acc in let res = Array.make (n+1) acc in
Array.iteri Array.iteri
(fun i x -> (fun i x ->
let new_acc = f res.(i) x in let new_acc = f res.(i) x in
res.(i + 1) <- new_acc) res.(i+1) <- new_acc)
a; a;
res res
(*$= & ~printer:Q.Print.(array int)
[|0;1;3;6|] (scan_left (+) 0 [|1;2;3|])
[|0|] (scan_left (+) 0 [||])
*)
let iter = Array.iter
let iteri = Array.iteri
let blit = Array.blit
let reverse_in_place a = let reverse_in_place a =
let len = Array.length a in let len = Array.length a in
if len > 0 then if len>0 then (
for k = 0 to (len - 1) / 2 do for k = 0 to (len-1)/2 do
let t = a.(k) in let t = a.(k) in
a.(k) <- a.(len - 1 - k); a.(k) <- a.(len-1-k);
a.(len - 1 - k) <- t a.(len-1-k) <- t;
done done
)
(*$T
reverse_in_place [| |]; true
reverse_in_place [| 1 |]; true
let a = [| 1; 2; 3; 4; 5 |] in \
reverse_in_place a; \
a = [| 5;4;3;2;1 |]
let a = [| 1; 2; 3; 4; 5; 6 |] in \
reverse_in_place a; \
a = [| 6;5;4;3;2;1 |]
*)
let sorted cmp a = let sorted cmp a =
let b = Array.copy a in let b = Array.copy a in
Array.sort cmp b; Array.sort cmp b;
b b
(*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] (sorted Pervasives.compare [||])
[|0;1;2;3;4|] (sorted Pervasives.compare [|3;2;1;4;0|])
*)
(*$Q
Q.(array int) (fun a -> \
let b = Array.copy a in \
Array.sort Pervasives.compare b; b = sorted Pervasives.compare a)
*)
let sort_indices cmp a = let sort_indices cmp a =
let len = Array.length a in let len = Array.length a in
let b = Array.init len (fun k -> k) in let b = Array.init len (fun k->k) in
Array.sort (fun k1 k2 -> cmp a.(k1) a.(k2)) b; Array.sort (fun k1 k2 -> cmp a.(k1) a.(k2)) b;
b b
let sort_ranking cmp a = sort_indices CCInt.compare (sort_indices cmp a) (*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] (sort_indices Pervasives.compare [||])
[|4;2;1;0;3|] (sort_indices Pervasives.compare [|"d";"c";"b";"e";"a"|])
*)
(*$Q
Q.(array printable_string) (fun a -> \
let b = sort_indices String.compare a in \
sorted String.compare a = Array.map (Array.get a) b)
*)
let sort_ranking cmp a =
let cmp_int : int -> int -> int = Pervasives.compare in
sort_indices cmp_int (sort_indices cmp a)
(*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] (sort_ranking Pervasives.compare [||])
[|3;2;1;4;0|] (sort_ranking Pervasives.compare [|"d";"c";"b";"e";"a"|])
*)
(*$Q
Q.(array_of_size Gen.(0--50) printable_string) (fun a -> \
let b = sort_ranking String.compare a in \
let a_sorted = sorted String.compare a in \
a = Array.map (Array.get a_sorted) b)
*)
let rev a = let rev a =
let b = Array.copy a in let b = Array.copy a in
reverse_in_place b; reverse_in_place b;
b b
exception Found (*$Q
Q.(array small_int) (fun a -> rev (rev a) = a)
*)
let mem ?(eq = Stdlib.( = )) elt a = (*$T
try rev [| 1; 2; 3 |] = [| 3; 2; 1 |]
Array.iter (fun e -> if eq e elt then raise_notrace Found) a; rev [| 1; 2; |] = [| 2; 1 |]
false rev [| |] = [| |]
with Found -> true *)
let rec find_aux f a i = let rec find_aux f a i =
if i >= Array.length a then if i = Array.length a then None
None else match f i a.(i) with
else (
match f i a.(i) with
| Some _ as res -> res | Some _ as res -> res
| None -> find_aux f a (i + 1) | None -> find_aux f a (i+1)
)
[@@@ocaml.warning "-32"] let find_map f a = find_aux (fun _ -> f ) a 0
let find_map f a = find_aux (fun _ -> f) a 0
let find = find_map let find = find_map
let find_map_i f a = find_aux f a 0 let find_map_i f a = find_aux f a 0
let findi = find_map_i let findi = find_map_i
[@@@ocaml.warning "+32"]
let find_idx p a = let find_idx p a =
find_aux find_aux (fun i x -> if p x then Some (i,x) else None) a 0
(fun i x ->
if p x then
Some (i, x)
else
None)
a 0
let max cmp a =
if Array.length a = 0 then
None
else
Some
(fold
(fun acc elt ->
if cmp acc elt < 0 then
elt
else
acc)
a.(0) a)
let max_exn cmp a =
match max cmp a with
| None -> invalid_arg "CCArray.max_exn"
| Some elt -> elt
let argmax cmp a =
if Array.length a = 0 then
None
else
Some
(foldi
(fun acc i elt ->
if cmp a.(acc) elt < 0 then
i
else
acc)
0 a)
let argmax_exn cmp a =
match argmax cmp a with
| None -> invalid_arg "CCArray.argmax_exn"
| Some elt -> elt
let min cmp a =
if Array.length a = 0 then
None
else
Some
(fold
(fun acc elt ->
if cmp acc elt > 0 then
elt
else
acc)
a.(0) a)
let min_exn cmp a =
match min cmp a with
| None -> invalid_arg "CCArray.min_exn"
| Some elt -> elt
let argmin cmp a =
if Array.length a = 0 then
None
else
Some
(foldi
(fun acc i elt ->
if cmp a.(acc) elt > 0 then
i
else
acc)
0 a)
let argmin_exn cmp a =
match argmin cmp a with
| None -> invalid_arg "CCArray.argmin_exn"
| Some elt -> elt
let filter_map f a = let filter_map f a =
let rec aux acc i = let rec aux acc i =
if i = Array.length a then ( if i = Array.length a
then (
let a' = Array.of_list acc in let a' = Array.of_list acc in
reverse_in_place a'; reverse_in_place a';
a' a'
) else ( ) else match f a.(i) with
match f a.(i) with | None -> aux acc (i+1)
| None -> aux acc (i + 1) | Some x -> aux (x::acc) (i+1)
| Some x -> aux (x :: acc) (i + 1) in aux [] 0
)
in (*$T
aux [] 0 filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \
[| 1; 2; 3; 4 |] = [| "2"; "4" |]
filter_map (fun x -> if x mod 2 = 0 then Some (string_of_int x) else None) \
[| 1; 2; 3; 4; 5; 6 |] \
= [| "2"; "4"; "6" |]
*)
let filter p a = let filter p a =
filter_map filter_map (fun x -> if p x then Some x else None) a
(fun x ->
if p x then
Some x
else
None)
a
(* append [rev a] in front of [acc] *) (* append [rev a] in front of [acc] *)
let rec __rev_append_list a acc i = let rec __rev_append_list a acc i =
if i = Array.length a then if i = Array.length a
acc then acc
else else
__rev_append_list a (a.(i) :: acc) (i + 1) __rev_append_list a (a.(i) :: acc) (i+1)
let flat_map f a = let flat_map f a =
let rec aux acc i = let rec aux acc i =
if i = Array.length a then ( if i = Array.length a
then (
let a' = Array.of_list acc in let a' = Array.of_list acc in
reverse_in_place a'; reverse_in_place a';
a' a'
) else (
let a' = f a.(i) in
aux (__rev_append_list a' acc 0) (i + 1)
) )
in else
aux [] 0 let a' = f a.(i) in
aux (__rev_append_list a' acc 0) (i+1)
in aux [] 0
let monoid_product f a1 a2 = (*$T
let na1 = length a1 in let a = [| 1; 3; 5 |] in \
init let a' = flat_map (fun x -> [| x; x+1 |]) a in \
(na1 * length a2) a' = [| 1; 2; 3; 4; 5; 6 |]
(fun i_prod -> *)
let i = i_prod mod na1 in
let j = i_prod / na1 in
f a1.(i) a2.(j))
let rec _lookup_rec ~cmp k a i j = let rec _lookup_rec ~cmp k a i j =
if i > j then if i>j then raise Not_found
raise Not_found else if i=j
else if i = j then then if cmp k a.(i) = 0
if cmp k a.(i) = 0 then then i
i else raise Not_found
else else
raise Not_found let middle = (j+i)/2 in
else (
let middle = (j + i) / 2 in
match cmp k a.(middle) with match cmp k a.(middle) with
| 0 -> middle | 0 -> middle
| n when n < 0 -> _lookup_rec ~cmp k a i (middle - 1) | n when n<0 -> _lookup_rec ~cmp k a i (middle-1)
| _ -> _lookup_rec ~cmp k a (middle + 1) j | _ -> _lookup_rec ~cmp k a (middle+1) j
)
let _lookup_exn ~cmp k a i j = let _lookup_exn ~cmp k a i j =
if i > j then raise Not_found; if i>j then raise Not_found;
match cmp k a.(i) with match cmp k a.(i) with
| 0 -> i | 0 -> i
| n when n < 0 -> raise Not_found (* too low *) | n when n<0 -> raise Not_found (* too low *)
| _ when i = j -> raise Not_found (* too high *) | _ when i=j -> raise Not_found (* too high *)
| _ -> | _ ->
(match cmp k a.(j) with match cmp k a.(j) with
| 0 -> j | 0 -> j
| n when n < 0 -> _lookup_rec ~cmp k a (i + 1) (j - 1) | n when n<0 -> _lookup_rec ~cmp k a (i+1) (j-1)
| _ -> raise Not_found) | _ -> raise Not_found (* too high *)
(* too high *)
let lookup_exn ~cmp k a = _lookup_exn ~cmp k a 0 (Array.length a - 1) let lookup_exn ?(cmp=Pervasives.compare) k a =
_lookup_exn ~cmp k a 0 (Array.length a-1)
let lookup ~cmp k a = let lookup ?(cmp=Pervasives.compare) k a =
try Some (_lookup_exn ~cmp k a 0 (Array.length a - 1)) try Some (_lookup_exn ~cmp k a 0 (Array.length a-1))
with Not_found -> None with Not_found -> None
let bsearch ~cmp k a = (*$T
lookup 2 [|0;1;2;3;4;5|] = Some 2
lookup 4 [|0;1;2;3;4;5|] = Some 4
lookup 0 [|1;2;3;4;5|] = None
lookup 6 [|1;2;3;4;5|] = None
lookup 3 [| |] = None
lookup 1 [| 1 |] = Some 0
lookup 2 [| 1 |] = None
*)
let bsearch ?(cmp=Pervasives.compare) k a =
let rec aux i j = let rec aux i j =
if i > j then if i > j
`Just_after j then `Just_after j
else ( else
let middle = i + ((j - i) / 2) in let middle = i + (j - i) / 2 in (* avoid overflow *)
(* avoid overflow *)
match cmp k a.(middle) with match cmp k a.(middle) with
| 0 -> `At middle | 0 -> `At middle
| n when n < 0 -> aux i (middle - 1) | n when n<0 -> aux i (middle - 1)
| _ -> aux (middle + 1) j | _ -> aux (middle + 1) j
)
in in
let n = Array.length a in let n = Array.length a in
if n = 0 then if n=0 then `Empty
`Empty else match cmp a.(0) k, cmp a.(n-1) k with
else ( | c, _ when c>0 -> `All_bigger
match cmp a.(0) k, cmp a.(n - 1) k with | _, c when c<0 -> `All_lower
| c, _ when c > 0 -> `All_bigger | _ -> aux 0 (n-1)
| _, c when c < 0 -> `All_lower
| _ -> aux 0 (n - 1) (*$T bsearch
) bsearch 3 [|1; 2; 2; 3; 4; 10|] = `At 3
bsearch 5 [|1; 2; 2; 3; 4; 10|] = `Just_after 4
bsearch 1 [|1; 2; 5; 5; 11; 12|] = `At 0
bsearch 12 [|1; 2; 5; 5; 11; 12|] = `At 5
bsearch 10 [|1; 2; 2; 3; 4; 9|] = `All_lower
bsearch 0 [|1; 2; 2; 3; 4; 9|] = `All_bigger
bsearch 3 [| |] = `Empty
*)
let (>>=) a f = flat_map f a
let (>>|) a f = map f a
let (>|=) a f = map f a
let for_all p a =
let rec aux i =
i = Array.length a || (p a.(i) && aux (i+1))
in
aux 0
let exists p a =
let rec aux i =
i <> Array.length a && (p a.(i) || aux (i+1))
in
aux 0
let rec _for_all2 p a1 a2 i1 i2 ~len = let rec _for_all2 p a1 a2 i1 i2 ~len =
len = 0 len=0 || (p a1.(i1) a2.(i2) && _for_all2 p a1 a2 (i1+1) (i2+1) ~len:(len-1))
|| (p a1.(i1) a2.(i2) && _for_all2 p a1 a2 (i1 + 1) (i2 + 1) ~len:(len - 1))
let for_all2 p a b = let for_all2 p a b =
Array.length a = Array.length b && _for_all2 p a b 0 0 ~len:(Array.length a) Array.length a = Array.length b
&&
_for_all2 p a b 0 0 ~len:(Array.length a)
let rec _exists2 p a1 a2 i1 i2 ~len = let rec _exists2 p a1 a2 i1 i2 ~len =
len > 0 len>0 && (p a1.(i1) a2.(i2) || _exists2 p a1 a2 (i1+1) (i2+1) ~len:(len-1))
&& (p a1.(i1) a2.(i2) || _exists2 p a1 a2 (i1 + 1) (i2 + 1) ~len:(len - 1))
let exists2 p a b = let exists2 p a b =
_exists2 p a b 0 0 ~len:(Stdlib.min (Array.length a) (Array.length b)) _exists2 p a b 0 0 ~len:(min (Array.length a) (Array.length b))
let _iter2 f a b i j ~len =
for o = 0 to len-1 do
f (Array.get a (i+o)) (Array.get b (j+o))
done
let _fold2 f acc a b i j ~len = let _fold2 f acc a b i j ~len =
let rec aux acc o = let rec aux acc o =
if o = len then if o=len then acc
acc else
else ( let acc = f acc (Array.get a (i+o)) (Array.get b (j+o)) in
let acc = f acc (Array.get a (i + o)) (Array.get b (j + o)) in aux acc (o+1)
aux acc (o + 1)
)
in in
aux acc 0 aux acc 0
let iter2 f a b =
if length a <> length b then invalid_arg "iter2";
_iter2 f a b 0 0 ~len:(Array.length a)
let fold2 f acc a b = let fold2 f acc a b =
if length a <> length b then invalid_arg "fold2"; if length a <> length b then invalid_arg "fold2";
_fold2 f acc a b 0 0 ~len:(Array.length a) _fold2 f acc a b 0 0 ~len:(Array.length a)
let ( -- ) i j = let (--) i j =
if i <= j then if i<=j
Array.init (j - i + 1) (fun k -> i + k) then
Array.init (j-i+1) (fun k -> i+k)
else else
Array.init (i - j + 1) (fun k -> i - k) Array.init (i-j+1) (fun k -> i-k)
let ( --^ ) i j = (*$T
if i = j then (1 -- 4) |> Array.to_list = [1;2;3;4]
[||] (4 -- 1) |> Array.to_list = [4;3;2;1]
else if i > j then (0 -- 0) |> Array.to_list = [0]
Array.init (i - j) (fun k -> i - k) *)
else
Array.init (j - i) (fun k -> i + k) (*$Q
Q.(pair small_int small_int) (fun (a,b) -> \
(a -- b) |> Array.to_list = CCList.(a -- b))
*)
let (--^) i j =
if i=j then [| |]
else if i>j
then Array.init (i-j) (fun k -> i-k)
else Array.init (j-i) (fun k -> i+k)
(*$Q
Q.(pair small_int small_int) (fun (a,b) -> \
(a --^ b) |> Array.to_list = CCList.(a --^ b))
*)
(** all the elements of a, but the i-th, into a list *) (** all the elements of a, but the i-th, into a list *)
let except_idx a i = let except_idx a i =
foldi foldi
(fun acc j elt -> (fun acc j elt -> if i = j then acc else elt::acc)
if i = j then
acc
else
elt :: acc)
[] a [] a
let equal eq a b = let equal eq a b =
let rec aux i = let rec aux i =
if i = Array.length a then if i = Array.length a then true
true else eq a.(i) b.(i) && aux (i+1)
else
eq a.(i) b.(i) && aux (i + 1)
in in
Array.length a = Array.length b && aux 0 Array.length a = Array.length b
&&
aux 0
(*$Q
Q.(pair (array small_int)(array small_int)) (fun (a,b) -> \
equal (=) a b = equal (=) b a)
*)
(*$T
equal (=) [|1|] [|1|]
*)
let compare cmp a b = let compare cmp a b =
let rec aux i = let rec aux i =
if i = Array.length a then if i = Array.length a
if i = Array.length b then then if i = Array.length b then 0 else -1
0 else if i = Array.length b
else then 1
-1 else
else if i = Array.length b then
1
else (
let c = cmp a.(i) b.(i) in let c = cmp a.(i) b.(i) in
if c = 0 then if c = 0 then aux (i+1) else c
aux (i + 1)
else
c
)
in in
aux 0 aux 0
(*$T
compare CCOrd.compare [| 1; 2; 3 |] [| 1; 2; 3 |] = 0
compare CCOrd.compare [| 1; 2; 3 |] [| 2; 2; 3 |] < 0
compare CCOrd.compare [| 1; 2; |] [| 1; 2; 3 |] < 0
compare CCOrd.compare [| 1; 2; 3 |] [| 1; 2; |] > 0
*)
(* swap elements of array *) (* swap elements of array *)
let swap a i j = let swap a i j =
if i <> j then ( if i<>j then (
let tmp = a.(i) in let tmp = a.(i) in
a.(i) <- a.(j); a.(i) <- a.(j);
a.(j) <- tmp a.(j) <- tmp;
) )
(* shuffle a[i … j] using the given int random generator (*$T
let a = [| 1;2;3 |] in \
swap a 0 1; \
a = [| 2;1;3 |]
let a = [| 1;2;3 |] in \
swap a 0 2; \
a = [| 3;2;1 |]
*)
(*$QR
Q.(array small_int) (fun a ->
let b = Array.copy a in
for i = 0 to Array.length a-1 do
for j = i+1 to Array.length a-1 do
swap a i j; done; done;
for i = 0 to Array.length a-1 do
for j = i+1 to Array.length a-1 do
swap a i j; done; done;
a=b)
*)
(* shuffle a[i...j[ using the given int random generator
See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle *) See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle *)
let _shuffle _rand_int a i j = let _shuffle _rand_int a i j =
for k = j - 1 downto i + 1 do for k = j-1 downto i+1 do
let l = _rand_int (k + 1) in let l = _rand_int (k+1) in
let tmp = a.(l) in let tmp = a.(l) in
a.(l) <- a.(k); a.(l) <- a.(k);
a.(k) <- tmp a.(k) <- tmp;
done done
let shuffle a = _shuffle Random.int a 0 (Array.length a) let shuffle a =
let shuffle_with st a = _shuffle (Random.State.int st) a 0 (Array.length a) _shuffle Random.int a 0 (Array.length a)
let random_choose a = let shuffle_with st a =
_shuffle (Random.State.int st) a 0 (Array.length a)
let rec _to_klist a i j () =
if i=j then `Nil else `Cons (a.(i), _to_klist a (i+1) j)
let random_choose a st =
let n = Array.length a in let n = Array.length a in
if n = 0 then invalid_arg "Array.random_choose"; if n = 0 then raise Not_found;
fun st -> a.(Random.State.int st n) a.(Random.State.int st n)
let random_len n g st = Array.init n (fun _ -> g st) let random_len n g st =
Array.init n (fun _ -> g st)
let random g st = let random g st =
let n = Random.State.int st 1_000 in let n = Random.State.int st 1_000 in
@ -434,47 +532,31 @@ let random_non_empty g st =
let n = 1 + Random.State.int st 1_000 in let n = 1 + Random.State.int st 1_000 in
random_len n g st random_len n g st
let pp ?(pp_start = fun _ () -> ()) ?(pp_stop = fun _ () -> ()) let pp ?(sep=", ") pp_item out a =
?(pp_sep = fun out () -> Format.fprintf out ",@ ") pp_item out a = for k = 0 to Array.length a-1 do
pp_start out (); if k > 0 then (Format.pp_print_string out sep; Format.pp_print_cut out ());
for k = 0 to Array.length a - 1 do
if k > 0 then pp_sep out ();
pp_item out a.(k) pp_item out a.(k)
done; done
pp_stop out ()
let pp_i ?(pp_start = fun _ () -> ()) ?(pp_stop = fun _ () -> ()) let pp_i ?(sep=", ") pp_item out a =
?(pp_sep = fun out () -> Format.fprintf out ",@ ") pp_item out a =
pp_start out ();
for k = 0 to Array.length a - 1 do for k = 0 to Array.length a - 1 do
if k > 0 then pp_sep out (); if k > 0 then (Format.pp_print_string out sep; Format.pp_print_cut out ());
pp_item k out a.(k) pp_item k out a.(k)
done; done
pp_stop out ()
let to_string ?(sep = ", ") item_to_string a = let to_seq a k = iter k a
Array.to_list a |> List.map item_to_string |> String.concat sep
let to_seq a =
let rec aux i () =
if i >= length a then
Seq.Nil
else
Seq.Cons (a.(i), aux (i + 1))
in
aux 0
let to_iter a k = iter k a
let to_gen a = let to_gen a =
let k = ref 0 in let k = ref 0 in
fun () -> fun () ->
if !k < Array.length a then ( if !k < Array.length a
then (
let x = a.(!k) in let x = a.(!k) in
incr k; incr k;
Some x Some x
) else ) else None
None
let to_klist a = _to_klist a 0 (Array.length a)
(** {2 Generic Functions} *) (** {2 Generic Functions} *)
@ -483,16 +565,18 @@ module type MONO_ARRAY = sig
type t type t
val length : t -> int val length : t -> int
val get : t -> int -> elt val get : t -> int -> elt
val set : t -> int -> elt -> unit val set : t -> int -> elt -> unit
end end
(* Dual Pivot Quicksort (Yaroslavskiy) (* Dual Pivot Quicksort (Yaroslavskiy)
from "average case analysis of Java 7's Dual Pivot Quicksort" *) from "average case analysis of Java 7's Dual Pivot Quicksort" *)
module SortGeneric (A : MONO_ARRAY) = struct module SortGeneric(A : MONO_ARRAY) = struct
module Rand = Random.State module Rand = Random.State
let seed_ = [| 123456 |] let seed_ = [|123456|]
type state = { type state = {
mutable l: int; (* left pointer *) mutable l: int; (* left pointer *)
@ -500,11 +584,10 @@ module SortGeneric (A : MONO_ARRAY) = struct
mutable k: int; mutable k: int;
} }
let rand_idx_ rand i j = i + Rand.int rand (j - i) let rand_idx_ rand i j = i + Rand.int rand (j-i)
let swap_ a i j = let swap_ a i j =
if i = j then if i=j then ()
()
else ( else (
let tmp = A.get a i in let tmp = A.get a i in
A.set a i (A.get a j); A.set a i (A.get a j);
@ -513,42 +596,36 @@ module SortGeneric (A : MONO_ARRAY) = struct
let sort ~cmp a = let sort ~cmp a =
let rec insert_ a i k = let rec insert_ a i k =
if k < i then if k<i then ()
() else if cmp (A.get a k) (A.get a (k+1)) > 0 then (
else if cmp (A.get a k) (A.get a (k + 1)) > 0 then ( swap_ a k (k+1);
swap_ a k (k + 1); insert_ a i (k-1)
insert_ a i (k - 1)
) )
in in
(* recursive part of insertion sort *) (* recursive part of insertion sort *)
let rec sort_insertion_rec a i j k = let rec sort_insertion_rec a i j k =
if k < j then ( if k<j then (
insert_ a i (k - 1); insert_ a i (k-1);
sort_insertion_rec a i j (k + 1) sort_insertion_rec a i j (k+1)
) )
in in
(* insertion sort, for small slices *) (* insertion sort, for small slices *)
let sort_insertion a i j = let sort_insertion a i j =
if j - i > 1 then sort_insertion_rec a i j (i + 1) if j-i > 1 then sort_insertion_rec a i j (i+1)
in in
let rand = Rand.make seed_ in let rand = Rand.make seed_ in
(* sort slice. (* sort slice.
There is a chance that the two pivots are equal, but it's unlikely. *) There is a chance that the two pivots are equal, but it's unlikely. *)
let rec sort_slice_ ~st a i j = let rec sort_slice_ ~st a i j =
if j - i > 10 then ( if j-i>10 then (
st.l <- i; st.l <- i;
st.g <- j - 1; st.g <- j-1;
st.k <- i; st.k <- i;
(* choose pivots *) (* choose pivots *)
let p = A.get a (rand_idx_ rand i j) in let p = A.get a (rand_idx_ rand i j) in
let q = A.get a (rand_idx_ rand i j) in let q = A.get a (rand_idx_ rand i j) in
(* invariant: st.p <= st.q, swap them otherwise *) (* invariant: st.p <= st.q, swap them otherwise *)
let p, q = let p, q = if cmp p q > 0 then q, p else p, q in
if cmp p q > 0 then
q, p
else
p, q
in
while st.k <= st.g do while st.k <= st.g do
let cur = A.get a st.k in let cur = A.get a st.k in
if cmp cur p < 0 then ( if cmp cur p < 0 then (
@ -574,35 +651,42 @@ module SortGeneric (A : MONO_ARRAY) = struct
(* save values before recursing *) (* save values before recursing *)
let l = st.l and g = st.g and sort_middle = cmp p q < 0 in let l = st.l and g = st.g and sort_middle = cmp p q < 0 in
sort_slice_ ~st a i l; sort_slice_ ~st a i l;
if sort_middle then sort_slice_ ~st a l (g + 1); if sort_middle then sort_slice_ ~st a l (g+1);
sort_slice_ ~st a (g + 1) j sort_slice_ ~st a (g+1) j;
) else ) else sort_insertion a i j
sort_insertion a i j
in in
if A.length a > 0 then ( if A.length a > 0 then (
let st = { l = 0; g = A.length a; k = 0 } in let st = { l=0; g=A.length a; k=0; } in
sort_slice_ ~st a 0 (A.length a) sort_slice_ ~st a 0 (A.length a)
) )
end end
let sort_generic (type arr elt)
(module A : MONO_ARRAY with type t = arr and type elt = elt) ~cmp a = let sort_generic (type arr)(type elt)
let module S = SortGeneric (A) in (module A : MONO_ARRAY with type t = arr and type elt = elt)
?(cmp=Pervasives.compare) a
=
let module S = SortGeneric(A) in
S.sort ~cmp a S.sort ~cmp a
module Infix = struct (*$inject
let ( >>= ) a f = flat_map f a module IA = struct
let ( >>| ) a f = map f a type elt = int
let ( >|= ) a f = map f a type t = int array
let ( -- ) = ( -- ) include Array
let ( --^ ) = ( --^ ) end
type 'a t = 'a array let gen_arr = Q.Gen.(array_size (1--100) small_int)
let arr_arbitrary = Q.make
~print:Q.Print.(array int)
~small:Array.length
~shrink:Q.Shrink.(array ?shrink:None)
gen_arr
*)
let ( let* ) = ( >>= ) (*$Q & ~count:300
let ( let+ ) = ( >|= ) arr_arbitrary (fun a -> \
let[@inline] ( and+ ) a1 a2 = monoid_product (fun x y -> x, y) a1 a2 let a1 = Array.copy a and a2 = Array.copy a in \
let ( and* ) = ( and+ ) Array.sort CCInt.compare a1; sort_generic ~cmp:CCInt.compare (module IA) a2; \
end a1 = a2 )
*)
include Infix

View file

@ -1,11 +1,10 @@
(* This file is free software, part of containers. See file "license" for more details. *) (* This file is free software, part of containers. See file "license" for more details. *)
(** Array utils *) (** {1 Array utils} *)
type 'a iter = ('a -> unit) -> unit
(** Fast internal iterator.
@since 2.8 *)
type 'a sequence = ('a -> unit) -> unit
type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]
type 'a gen = unit -> 'a option type 'a gen = unit -> 'a option
type 'a equal = 'a -> 'a -> bool type 'a equal = 'a -> 'a -> bool
type 'a ord = 'a -> 'a -> int type 'a ord = 'a -> 'a -> int
@ -15,293 +14,226 @@ type 'a printer = Format.formatter -> 'a -> unit
(** {2 Arrays} *) (** {2 Arrays} *)
include module type of Array include module type of Array
(** @inline *)
type 'a t = 'a array
val empty : 'a t val empty : 'a t
(** [empty] is the empty array, physically equal to [[||]]. *)
val equal : 'a equal -> 'a t equal val equal : 'a equal -> 'a t equal
(** [equal eq a1 a2] is [true] if the lengths of [a1] and [a2] are the same
and if their corresponding elements test equal, using [eq]. *)
val compare : 'a ord -> 'a t ord val compare : 'a ord -> 'a t ord
(** [compare cmp a1 a2] compares arrays [a1] and [a2] using the function comparison [cmp]. *)
val swap : 'a t -> int -> int -> unit val swap : 'a t -> int -> int -> unit
(** [swap a i j] swaps elements at indices [i] and [j]. (** [swap arr i j] swaps elements at indices [i] and [j].
@since 1.4 *) @since 1.4 *)
val get : 'a t -> int -> 'a
val get_safe : 'a t -> int -> 'a option val get_safe : 'a t -> int -> 'a option
(** [get_safe a i] returns [Some a.(i)] if [i] is a valid index. (** [get_safe a i] returns [Some a.(i)] if [i] is a valid index
@since 0.18 *) @since 0.18 *)
val map_inplace : ('a -> 'a) -> 'a t -> unit val set : 'a t -> int -> 'a -> unit
(** [map_inplace f a] replace all elements of [a] by its image by [f].
@since 3.8 *)
val mapi_inplace : (int -> 'a -> 'a) -> 'a t -> unit val length : _ t -> int
(** [mapi_inplace f a] replace all elements of [a] by its image by [f].
@since 3.10 *)
val fold : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a val fold : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a
(** [fold f init a] computes [f ((f (f init a.(0)) a.(1))) a.(n-1)],
where [n] is the length of the array [a].
Same as {!Array.fold_left}*)
val foldi : ('a -> int -> 'b -> 'a) -> 'a -> 'b t -> 'a val foldi : ('a -> int -> 'b -> 'a) -> 'a -> 'b t -> 'a
(** [foldi f init a] is just like {!fold}, but it also passes in the index (** Fold left on array, with index *)
of each element as the second argument to the folded function [f]. *)
val fold_while : ('a -> 'b -> 'a * [ `Stop | `Continue ]) -> 'a -> 'b t -> 'a val fold_while : ('a -> 'b -> 'a * [`Stop | `Continue]) -> 'a -> 'b t -> 'a
(** [fold_while f init a] folds left on array [a] until a stop condition via [('a, `Stop)] (** Fold left on array until a stop condition via [('a, `Stop)] is
is indicated by the accumulator. indicated by the accumulator
@since 0.8 *) @since 0.8 *)
val fold_map : ('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a t -> 'acc * 'b t val fold_map : ('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a t -> 'acc * 'b t
(** [fold_map f init a] is a [fold_left]-like function, but it also maps the (** [fold_map f acc a] is a [fold_left]-like function, but it also maps the
array to another array. array to another array.
@since 1.2, but only @since 1.2 *)
@since 2.1 with labels *)
val scan_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a t -> 'acc t val scan_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a t -> 'acc t
(** [scan_left f init a] returns the array (** [scan_left f acc a] returns the array
[ [|init; f init x0; f (f init a.(0)) a.(1); |] ]. [ [|acc; f acc x0; f (f acc a.(0)) a.(1); |] ]
@since 1.2 *)
@since 1.2, but only
@since 2.1 with labels *) val iter : ('a -> unit) -> 'a t -> unit
val iteri : (int -> 'a -> unit) -> 'a t -> unit
val blit : 'a t -> int -> 'a t -> int -> int -> unit
(** [blit from i into j len] copies [len] elements from the first array
to the second. See {!Array.blit}. *)
val reverse_in_place : 'a t -> unit val reverse_in_place : 'a t -> unit
(** [reverse_in_place a] reverses the array [a] in place. *) (** Reverse the array in place *)
val sorted : ('a -> 'a -> int) -> 'a t -> 'a array val sorted : ('a -> 'a -> int) -> 'a t -> 'a array
(** [sorted f a] makes a copy of [a] and sorts it with [f]. (** [sorted cmp a] makes a copy of [a] and sorts it with [cmp].
@since 1.0 *) @since 1.0 *)
val sort_indices : ('a -> 'a -> int) -> 'a t -> int array val sort_indices : ('a -> 'a -> int) -> 'a t -> int array
(** [sort_indices f a] returns a new array [b], with the same length as [a], (** [sort_indices cmp a] returns a new array [b], with the same length as [a],
such that [b.(i)] is the index at which the [i]-th element of [sorted f a] such that [b.(i)] is the index at which the [i]-th element of [sorted cmp a]
appears in [a]. [a] is not modified. appears in [a]. [a] is not modified.
In other words, [map (fun i -> a.(i)) (sort_indices f a) = sorted f a]. In other words, [map (fun i -> a.(i)) (sort_indices cmp a) = sorted cmp a].
[sort_indices] yields the inverse permutation of {!sort_ranking}. [sort_indices] yields the inverse permutation of {!sort_ranking}.
@since 1.0 *) @since 1.0 *)
val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array val sort_ranking : ('a -> 'a -> int) -> 'a t -> int array
(** [sort_ranking f a] returns a new array [b], with the same length as [a], (** [sort_ranking cmp a] returns a new array [b], with the same length as [a],
such that [b.(i)] is the index at which the [i]-th element of [a] appears such that [b.(i)] is the index at which the [i]-the element of [a] appears
in [sorted f a]. [a] is not modified. in [sorted cmp a]. [a] is not modified.
In other words, [map (fun i -> (sorted f a).(i)) (sort_ranking f a) = a]. In other words, [map (fun i -> (sorted cmp a).(i)) (sort_ranking cmp a) = a].
[sort_ranking] yields the inverse permutation of {!sort_indices}. [sort_ranking] yields the inverse permutation of {!sort_indices}.
In the absence of duplicate elements in [a], we also have In the absence of duplicate elements in [a], we also have
[lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)]. [lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)]
@since 1.0 *) @since 1.0 *)
val mem : ?eq:('a -> 'a -> bool) -> 'a -> 'a t -> bool
(** [mem ~eq x a] return true if x is present in [a]. Linear time.
@since 3.0
*)
val find_map : ('a -> 'b option) -> 'a t -> 'b option val find_map : ('a -> 'b option) -> 'a t -> 'b option
(** [find_map f a] returns [Some y] if there is an element [x] such (** [find_map f a] returns [Some y] if there is an element [x] such
that [f x = Some y]. Otherwise returns [None]. that [f x = Some y], else it returns [None]
@since 1.3, but only @since 1.3
@since 2.1 with labels *) *)
val find : ('a -> 'b option) -> 'a t -> 'b option
(** Alias to {!find_map}
@deprecated since 1.3 *)
val find_map_i : (int -> 'a -> 'b option) -> 'a t -> 'b option val find_map_i : (int -> 'a -> 'b option) -> 'a t -> 'b option
(** [find_map_i f a] is like {!find_map}, but the index of the element is also passed (** Like {!find_map}, but also pass the index to the predicate function.
to the predicate function [f]. @since 1.3 *)
@since 1.3, but only
@since 2.1 with labels *) val findi : (int -> 'a -> 'b option) -> 'a t -> 'b option
(** Alias to {!find_map_i}
@since 0.3.4
@deprecated since 1.3 *)
val find_idx : ('a -> bool) -> 'a t -> (int * 'a) option val find_idx : ('a -> bool) -> 'a t -> (int * 'a) option
(** [find_idx f a] returns [Some (i,x)] where [x] is the [i]-th element of [a], (** [find_idx p x] returns [Some (i,x)] where [x] is the [i]-th element of [l],
and [f x] holds. Otherwise returns [None]. and [p x] holds. Otherwise returns [None]
@since 0.3.4 *) @since 0.3.4 *)
val max : ('a -> 'a -> int) -> 'a t -> 'a option val lookup : ?cmp:'a ord -> 'a -> 'a t -> int option
(** [max cmp a] returns [None] if [a] is empty, otherwise, returns [Some e] where [e] (** Lookup the index of some value in a sorted array.
is a maximum element in [a] with respect to [cmp]. @return [None] if the key is not present, or
@since 3.12 *) [Some i] ([i] the index of the key) otherwise *)
val max_exn : ('a -> 'a -> int) -> 'a t -> 'a val lookup_exn : ?cmp:'a ord -> 'a -> 'a t -> int
(** [max_exn cmp a] is like {!max}, but (** Same as {!lookup}, but
@raise Invalid_argument if [a] is empty. @raise Not_found if the key is not present *)
@since 3.12 *)
val argmax : ('a -> 'a -> int) -> 'a t -> int option val bsearch : ?cmp:('a -> 'a -> int) -> 'a -> 'a t ->
(** [argmax cmp a] returns [None] if [a] is empty, otherwise, returns [Some i] where [i]
is the index of a maximum element in [a] with respect to [cmp].
@since 3.12 *)
val argmax_exn : ('a -> 'a -> int) -> 'a t -> int
(** [argmax_exn cmp a] is like {!argmax}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val min : ('a -> 'a -> int) -> 'a t -> 'a option
(** [min cmp a] returns [None] if [a] is empty, otherwise, returns [Some e] where [e]
is a minimum element in [a] with respect to [cmp].
@since 3.12 *)
val min_exn : ('a -> 'a -> int) -> 'a t -> 'a
(** [min_exn cmp a] is like {!min}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val argmin : ('a -> 'a -> int) -> 'a t -> int option
(** [argmin cmp a] returns [None] if [a] is empty, otherwise, returns [Some i] where [i]
is the index of a minimum element in [a] with respect to [cmp].
@since 3.12 *)
val argmin_exn : ('a -> 'a -> int) -> 'a t -> int
(** [argmin_exn cmp a] is like {!argmin}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val lookup : cmp:'a ord -> 'a -> 'a t -> int option
(** [lookup ~cmp key a] lookups the index of some key [key] in a sorted array [a].
Undefined behavior if the array [a] is not sorted wrt [~cmp].
Complexity: [O(log (n))] (dichotomic search).
@return [None] if the key [key] is not present, or
[Some i] ([i] the index of the key) otherwise. *)
val lookup_exn : cmp:'a ord -> 'a -> 'a t -> int
(** [lookup_exn ~cmp key a] is like {!lookup}, but
@raise Not_found if the key [key] is not present. *)
val bsearch :
cmp:('a -> 'a -> int) ->
'a ->
'a t ->
[ `All_lower | `All_bigger | `Just_after of int | `Empty | `At of int ] [ `All_lower | `All_bigger | `Just_after of int | `Empty | `At of int ]
(** [bsearch ~cmp key a] finds the index of the object [key] in the array [a], (** [bsearch ?cmp x arr] finds the index of the object [x] in the array [arr],
provided [a] is {b sorted} using [cmp]. If the array is not sorted, provided [arr] is {b sorted} using [cmp]. If the array is not sorted,
the result is not specified (may raise Invalid_argument). the result is not specified (may raise Invalid_argument).
Complexity: [O(log n)] where n is the length of the array [a] Complexity: O(log n) where n is the length of the array
(dichotomic search). (dichotomic search).
@return @return
- [`At i] if [cmp a.(i) key = 0] (for some i). - [`At i] if [cmp arr.(i) x = 0] (for some i)
- [`All_lower] if all elements of [a] are lower than [key]. - [`All_lower] if all elements of [arr] are lower than [x]
- [`All_bigger] if all elements of [a] are bigger than [key]. - [`All_bigger] if all elements of [arr] are bigger than [x]
- [`Just_after i] if [a.(i) < key < a.(i+1)]. - [`Just_after i] if [arr.(i) < x < arr.(i+1)]
- [`Empty] if the array [a] is empty. - [`Empty] if the array is empty
@raise Invalid_argument if the array is found to be unsorted w.r.t [cmp]. @raise Invalid_argument if the array is found to be unsorted w.r.t [cmp]
@since 0.13 *) @since 0.13 *)
val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool val for_all : ('a -> bool) -> 'a t -> bool
(** [for_all2 f [|a1; …; an|] [|b1; …; bn|]] is [true] if each pair of elements [ai bi]
satisfies the predicate [f].
That is, it returns [(f a1 b1) && (f a2 b2) && && (f an bn)].
@raise Invalid_argument if arrays have distinct lengths. val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
Allow different types. (** Forall on pairs of arrays.
@since 0.20 *) @raise Invalid_argument if they have distinct lengths
allow different types @since 0.20 *)
val exists : ('a -> bool) -> 'a t -> bool
val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
(** [exists2 f [|a1; …; an|] [|b1; …; bn|]] is [true] if any pair of elements [ai bi] (** Exists on pairs of arrays.
satisfies the predicate [f]. @raise Invalid_argument if they have distinct lengths
That is, it returns [(f a1 b1) || (f a2 b2) || || (f an bn)]. allow different types @since 0.20 *)
@raise Invalid_argument if arrays have distinct lengths.
Allow different types.
@since 0.20 *)
val fold2 : ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a t -> 'b t -> 'acc val fold2 : ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a t -> 'b t -> 'acc
(** [fold2 f init a b] fold on two arrays [a] and [b] stepwise. (** Fold on two arrays stepwise.
It computes [f ( (f init a1 b1) ) an bn]. @raise Invalid_argument if they have distinct lengths
@since 0.20 *)
@raise Invalid_argument if [a] and [b] have distinct lengths. val iter2 : ('a -> 'b -> unit) -> 'a t -> 'b t -> unit
(** Iterate on two arrays stepwise.
@raise Invalid_argument if they have distinct lengths
@since 0.20 *) @since 0.20 *)
val shuffle : 'a t -> unit val shuffle : 'a t -> unit
(** [shuffle a] randomly shuffles the array [a], in place. *) (** Shuffle randomly the array, in place *)
val shuffle_with : Random.State.t -> 'a t -> unit val shuffle_with : Random.State.t -> 'a t -> unit
(** [shuffle_with rs a] randomly shuffles the array [a] (like {!shuffle}) but a specialized random (** Like shuffle but using a specialized random state *)
state [rs] is used to control the random numbers being produced during shuffling (for reproducibility). *)
val random_choose : 'a t -> 'a random_gen val random_choose : 'a t -> 'a random_gen
(** [random_choose a rs] randomly chooses an element of [a]. (** Choose an element randomly.
@raise Not_found if the array/slice is empty. *) @raise Not_found if the array/slice is empty *)
val to_string : ?sep:string -> ('a -> string) -> 'a array -> string
(** [to_string ~sep item_to_string a] print [a] to a string using [sep] as a separator
between elements of [a].
@since 2.7 *)
val to_iter : 'a t -> 'a iter
(** [to_iter a] returns an [iter] of the elements of an array [a].
The input array [a] is shared with the sequence and modification of it will result
in modification of the iterator.
@since 2.8 *)
val to_seq : 'a t -> 'a Seq.t
(** [to_seq a] returns a [Seq.t] of the elements of an array [a].
The input array [a] is shared with the sequence and modification of it will result
in modification of the sequence.
Renamed from [to_std_seq] since 3.0.
@since 3.0
*)
val to_seq : 'a t -> 'a sequence
val to_gen : 'a t -> 'a gen val to_gen : 'a t -> 'a gen
(** [to_gen a] returns a [gen] of the elements of an array [a]. *) val to_klist : 'a t -> 'a klist
(** {2 IO} *) (** {2 IO} *)
val pp : val pp: ?sep:string -> 'a printer -> 'a t printer
?pp_start:unit printer -> (** Print an array of items with printing function *)
?pp_stop:unit printer ->
?pp_sep:unit printer ->
'a printer ->
'a t printer
(** [pp ~pp_start ~pp_stop ~pp_sep pp_item ppf a] formats the array [a] on [ppf].
Each element is formatted with [pp_item], [pp_start] is called at the beginning,
[pp_stop] is called at the end, [pp_sep] is called between each elements.
By defaults [pp_start] and [pp_stop] does nothing and [pp_sep] defaults to
(fun out -> Format.fprintf out ",@ "). *)
val pp_i : val pp_i: ?sep:string -> (int -> 'a printer) -> 'a t printer
?pp_start:unit printer -> (** Print an array, giving the printing function both index and item *)
?pp_stop:unit printer ->
?pp_sep:unit printer -> val map : ('a -> 'b) -> 'a t -> 'b t
(int -> 'a printer) ->
'a t printer val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
(** [pp_i ~pp_start ~pp_stop ~pp_sep pp_item ppf a] prints the array [a] on [ppf]. (** Map on two arrays stepwise.
The printing function [pp_item] is giving both index and element. @raise Invalid_argument if they have distinct lengths
[pp_start] is called at the beginning, @since 0.20 *)
[pp_stop] is called at the end, [pp_sep] is called between each elements.
By defaults [pp_start] and [pp_stop] does nothing and [pp_sep] defaults to
(fun out -> Format.fprintf out ",@ "). *)
val rev : 'a t -> 'a t val rev : 'a t -> 'a t
(** [rev a] copies the array [a] and reverses it in place. (** Copy + reverse in place
@since 0.20 *) @since 0.20 *)
val filter : ('a -> bool) -> 'a t -> 'a t val filter : ('a -> bool) -> 'a t -> 'a t
(** [filter f a] filters elements out of the array [a]. Only the elements satisfying (** Filter elements out of the array. Only the elements satisfying
the given predicate [f] will be kept. *) the given predicate will be kept. *)
val filter_map : ('a -> 'b option) -> 'a t -> 'b t val filter_map : ('a -> 'b option) -> 'a t -> 'b t
(** [filter_map f [|a1; …; an|]] calls [(f a1)(f an)] and returns an array [b] consisting (** Map each element into another value, or discard it *)
of all elements [bi] such as [f ai = Some bi]. When [f] returns [None], the corresponding
element of [a] is discarded. *)
val monoid_product : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
(** [monoid_product f a b] passes all combinaisons of tuples from the two arrays [a] and [b]
to the function [f].
@since 2.8 *)
val flat_map : ('a -> 'b t) -> 'a t -> 'b array val flat_map : ('a -> 'b t) -> 'a t -> 'b array
(** [flat_map f a] transforms each element of [a] into an array, then flattens. *) (** Transform each element into an array, then flatten *)
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
(** Infix version of {!flat_map} *)
val (>>|) : 'a t -> ('a -> 'b) -> 'b t
(** Infix version of {!map}
@since 0.8 *)
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
(** Infix version of {!map}
@since 0.8 *)
val except_idx : 'a t -> int -> 'a list val except_idx : 'a t -> int -> 'a list
(** [except_idx a i] removes the element of [a] at given index [i], and returns (** Remove given index, obtaining the list of the other elements *)
the list of the other elements. *)
val (--) : int -> int -> int t
(** Range array *)
val (--^) : int -> int -> int t
(** Range array, excluding right bound
@since 0.17 *)
val random : 'a random_gen -> 'a t random_gen val random : 'a random_gen -> 'a t random_gen
val random_non_empty : 'a random_gen -> 'a t random_gen val random_non_empty : 'a random_gen -> 'a t random_gen
@ -314,48 +246,15 @@ module type MONO_ARRAY = sig
type t type t
val length : t -> int val length : t -> int
val get : t -> int -> elt val get : t -> int -> elt
val set : t -> int -> elt -> unit val set : t -> int -> elt -> unit
end end
val sort_generic : val sort_generic :
(module MONO_ARRAY with type t = 'arr and type elt = 'elt) -> (module MONO_ARRAY with type t = 'arr and type elt = 'elt) ->
cmp:('elt -> 'elt -> int) -> ?cmp:('elt -> 'elt -> int) -> 'arr -> unit
'arr -> (** Sort the array, without allocating (eats stack space though). Performance
unit might be lower than {!Array.sort}.
(** [sort_generic (module M) ~cmp a] sorts the array [a], without allocating (eats stack space though).
Performance might be lower than {!Array.sort}.
@since 0.14 *) @since 0.14 *)
(** {3 Infix Operators}
It is convenient to [open CCArray.Infix] to access the infix operators
without cluttering the scope too much.
@since 2.7 *)
module Infix : sig
val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
(** [a >>= f] is the infix version of {!flat_map}. *)
val ( >>| ) : 'a t -> ('a -> 'b) -> 'b t
(** [a >>| f] is the infix version of {!map}.
@since 0.8 *)
val ( >|= ) : 'a t -> ('a -> 'b) -> 'b t
(** [a >|= f] is the infix version of {!map}.
@since 0.8 *)
val ( -- ) : int -> int -> int t
(** [x -- y] creates an array containing integers in the range [x .. y]. Bounds included. *)
val ( --^ ) : int -> int -> int t
(** [x --^ y] creates an array containing integers in the range [x .. y]. Right bound excluded.
@since 0.17 *)
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
val ( and* ) : 'a t -> 'b t -> ('a * 'b) t
end
include module type of Infix

View file

@ -1,3 +1,4 @@
(* This file is free software, part of containers. See file "license" for more details. *) (* This file is free software, part of containers. See file "license" for more details. *)
include CCArray include CCArray

View file

@ -1,11 +1,10 @@
(* This file is free software, part of containers. See file "license" for more details. *) (* This file is free software, part of containers. See file "license" for more details. *)
(** Array utils (Labeled version of {!CCArray}) *) (** {1 Array utils} *)
type 'a iter = ('a -> unit) -> unit
(** Fast internal iterator.
@since 2.8 *)
type 'a sequence = ('a -> unit) -> unit
type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]
type 'a gen = unit -> 'a option type 'a gen = unit -> 'a option
type 'a equal = 'a -> 'a -> bool type 'a equal = 'a -> 'a -> bool
type 'a ord = 'a -> 'a -> int type 'a ord = 'a -> 'a -> int
@ -14,310 +13,198 @@ type 'a printer = Format.formatter -> 'a -> unit
(** {2 Arrays} *) (** {2 Arrays} *)
include module type of ArrayLabels with module Floatarray = Array.Floatarray include module type of ArrayLabels
(** @inline *)
type 'a t = 'a array
val empty : 'a t val empty : 'a t
(** [empty] is the empty array, physically equal to [[||]]. *)
val equal : 'a equal -> 'a t equal val equal : 'a equal -> 'a t equal
(** [equal eq a1 a2] is [true] if the lengths of [a1] and [a2] are the same
and if their corresponding elements test equal, using [eq]. *)
val compare : 'a ord -> 'a t ord val compare : 'a ord -> 'a t ord
(** [compare cmp a1 a2] compares arrays [a1] and [a2] using the function comparison [cmp]. *)
val swap : 'a t -> int -> int -> unit val get : 'a t -> int -> 'a
(** [swap a i j] swaps elements at indices [i] and [j].
@since 1.4 *)
val get_safe : 'a t -> int -> 'a option val get_safe : 'a t -> int -> 'a option
(** [get_safe a i] returns [Some a.(i)] if [i] is a valid index. (** [get_safe a i] returns [Some a.(i)] if [i] is a valid index
@since 0.18 *) @since 0.18 *)
val map_inplace : f:('a -> 'a) -> 'a t -> unit val set : 'a t -> int -> 'a -> unit
(** [map_inplace ~f a] replace all elements of [a] by its image by [f].
@since 3.8 *)
val mapi_inplace : f:(int -> 'a -> 'a) -> 'a t -> unit val length : _ t -> int
(** [mapi_inplace ~f a] replace all elements of [a] by its image by [f].
@since 3.10 *)
val fold : f:('a -> 'b -> 'a) -> init:'a -> 'b t -> 'a val fold : f:('a -> 'b -> 'a) -> init:'a -> 'b t -> 'a
(** [fold ~f ~init a] computes [f ((f (f init a.(0)) a.(1))) a.(n-1)],
where [n] is the length of the array [a].
Same as {!ArrayLabels.fold_left} *)
val foldi : f:('a -> int -> 'b -> 'a) -> init:'a -> 'b t -> 'a val foldi : f:('a -> int -> 'b -> 'a) -> init:'a -> 'b t -> 'a
(** [foldi ~f ~init a] is just like {!fold}, but it also passes in the index (** Fold left on array, with index *)
of each element as the second argument to the folded function [f]. *)
val fold_while : val fold_while : f:('a -> 'b -> 'a * [`Stop | `Continue]) -> init:'a -> 'b t -> 'a
f:('a -> 'b -> 'a * [ `Stop | `Continue ]) -> init:'a -> 'b t -> 'a (** Fold left on array until a stop condition via [('a, `Stop)] is
(** [fold_while ~f ~init a] folds left on array [a] until a stop condition via [('a, `Stop)] indicated by the accumulator
is indicated by the accumulator.
@since 0.8 *) @since 0.8 *)
val fold_map : f:('acc -> 'a -> 'acc * 'b) -> init:'acc -> 'a t -> 'acc * 'b t val iter : f:('a -> unit) -> 'a t -> unit
(** [fold_map ~f ~init a] is a [fold_left]-like function, but it also maps the
array to another array.
@since 1.2, but only
@since 2.1 with labels *)
val scan_left : f:('acc -> 'a -> 'acc) -> init:'acc -> 'a t -> 'acc t val iteri : f:(int -> 'a -> unit) -> 'a t -> unit
(** [scan_left ~f ~init a] returns the array
[ [|init; f init x0; f (f init a.(0)) a.(1); |] ].
@since 1.2, but only val blit : 'a t -> int -> 'a t -> int -> int -> unit
@since 2.1 with labels *) (** [blit from i into j len] copies [len] elements from the first array
to the second. See {!Array.blit}. *)
val reverse_in_place : 'a t -> unit val reverse_in_place : 'a t -> unit
(** [reverse_in_place a] reverses the array [a] in place. *) (** Reverse the array in place *)
val sorted : f:('a -> 'a -> int) -> 'a t -> 'a array val sorted : f:('a -> 'a -> int) -> 'a t -> 'a array
(** [sorted ~f a] makes a copy of [a] and sorts it with [f]. (** [sorted cmp a] makes a copy of [a] and sorts it with [cmp].
@since 1.0 *) @since 1.0 *)
val sort_indices : f:('a -> 'a -> int) -> 'a t -> int array val sort_indices : f:('a -> 'a -> int) -> 'a t -> int array
(** [sort_indices ~f a] returns a new array [b], with the same length as [a], (** [sort_indices cmp a] returns a new array [b], with the same length as [a],
such that [b.(i)] is the index at which the [i]-th element of [sorted f a] such that [b.(i)] is the index of the [i]-th element of [a] in [sort cmp a].
appears in [a]. [a] is not modified. In other words, [map (fun i -> a.(i)) (sort_indices a) = sorted cmp a].
[a] is not modified.
In other words, [map (fun i -> a.(i)) (sort_indices f a) = sorted f a].
[sort_indices] yields the inverse permutation of {!sort_ranking}.
@since 1.0 *) @since 1.0 *)
val sort_ranking : f:('a -> 'a -> int) -> 'a t -> int array val sort_ranking : f:('a -> 'a -> int) -> 'a t -> int array
(** [sort_ranking ~f a] returns a new array [b], with the same length as [a], (** [sort_ranking cmp a] returns a new array [b], with the same length as [a],
such that [b.(i)] is the index at which the [i]-th element of [a] appears such that [b.(i)] is the position in [sorted cmp a] of the [i]-th
in [sorted f a]. [a] is not modified. element of [a].
[a] is not modified.
In other words, [map (fun i -> (sorted f a).(i)) (sort_ranking f a) = a]. In other words, [map (fun i -> (sorted cmp a).(i)) (sort_ranking cmp a) = a].
[sort_ranking] yields the inverse permutation of {!sort_indices}.
In the absence of duplicate elements in [a], we also have Without duplicates, we also have
[lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)]. [lookup_exn a.(i) (sorted a) = (sorted_ranking a).(i)]
@since 1.0 *) @since 1.0 *)
val mem : ?eq:('a -> 'a -> bool) -> 'a -> 'a t -> bool val find : f:('a -> 'b option) -> 'a t -> 'b option
(** [mem ~eq x a] return true if x is present in [a]. Linear time. (** [find f a] returns [Some y] if there is an element [x] such
@since 3.0 that [f x = Some y], else it returns [None] *)
*)
val find_map : f:('a -> 'b option) -> 'a t -> 'b option val findi : f:(int -> 'a -> 'b option) -> 'a t -> 'b option
(** [find_map ~f a] returns [Some y] if there is an element [x] such (** Like {!find}, but also pass the index to the predicate function.
that [f x = Some y]. Otherwise returns [None].
@since 1.3, but only
@since 2.1 with labels *)
val find_map_i : f:(int -> 'a -> 'b option) -> 'a t -> 'b option
(** [find_map_i ~f a] is like {!find_map}, but the index of the element is also passed
to the predicate function [f].
@since 1.3, but only
@since 2.1 with labels *)
val find_idx : f:('a -> bool) -> 'a t -> (int * 'a) option
(** [find_idx ~f a] returns [Some (i,x)] where [x] is the [i]-th element of [a],
and [f x] holds. Otherwise returns [None].
@since 0.3.4 *) @since 0.3.4 *)
val max : cmp:('a -> 'a -> int) -> 'a t -> 'a option val find_idx : f:('a -> bool) -> 'a t -> (int * 'a) option
(** [max ~cmp a] returns [None] if [a] is empty, otherwise, returns [Some e] where [e] (** [find_idx p x] returns [Some (i,x)] where [x] is the [i]-th element of [l],
is a maximum element in [a] with respect to [cmp]. and [p x] holds. Otherwise returns [None]
@since 3.12 *) @since 0.3.4 *)
val max_exn : cmp:('a -> 'a -> int) -> 'a t -> 'a val lookup : ?cmp:'a ord -> key:'a -> 'a t -> int option
(** [max_exn ~cmp a] is like {!max}, but (** Lookup the index of some value in a sorted array.
@raise Invalid_argument if [a] is empty. @return [None] if the key is not present, or
@since 3.12 *) [Some i] ([i] the index of the key) otherwise *)
val argmax : cmp:('a -> 'a -> int) -> 'a t -> int option val lookup_exn : ?cmp:'a ord -> key:'a -> 'a t -> int
(** [argmax ~cmp a] returns [None] if [a] is empty, otherwise, returns [Some i] where [i] (** Same as {!lookup_exn}, but
is the index of a maximum element in [a] with respect to [cmp]. @raise Not_found if the key is not present *)
@since 3.12 *)
val argmax_exn : cmp:('a -> 'a -> int) -> 'a t -> int val bsearch : ?cmp:('a -> 'a -> int) -> key:'a -> 'a t ->
(** [argmax_exn ~cmp a] is like {!argmax}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val min : cmp:('a -> 'a -> int) -> 'a t -> 'a option
(** [min ~cmp a] returns [None] if [a] is empty, otherwise, returns [Some e] where [e]
is a minimum element in [a] with respect to [cmp].
@since 3.12 *)
val min_exn : cmp:('a -> 'a -> int) -> 'a t -> 'a
(** [min_exn ~cmp a] is like {!min}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val argmin : cmp:('a -> 'a -> int) -> 'a t -> int option
(** [argmin ~cmp a] returns [None] if [a] is empty, otherwise, returns [Some i] where [i]
is the index of a minimum element in [a] with respect to [cmp].
@since 3.12 *)
val argmin_exn : cmp:('a -> 'a -> int) -> 'a t -> int
(** [argmin_exn ~cmp a] is like {!argmin}, but
@raise Invalid_argument if [a] is empty.
@since 3.12 *)
val lookup : cmp:('a ord[@keep_label]) -> key:'a -> 'a t -> int option
(** [lookup ~cmp ~key a] lookups the index of some key [key] in a sorted array [a].
Undefined behavior if the array [a] is not sorted wrt [cmp].
Complexity: [O(log (n))] (dichotomic search).
@return [None] if the key [key] is not present, or
[Some i] ([i] the index of the key) otherwise. *)
val lookup_exn : cmp:('a ord[@keep_label]) -> key:'a -> 'a t -> int
(** [lookup_exn ~cmp ~key a] is like {!lookup}, but
@raise Not_found if the key [key] is not present. *)
val bsearch :
cmp:(('a -> 'a -> int)[@keep_label]) ->
key:'a ->
'a t ->
[ `All_lower | `All_bigger | `Just_after of int | `Empty | `At of int ] [ `All_lower | `All_bigger | `Just_after of int | `Empty | `At of int ]
(** [bsearch ~cmp ~key a] finds the index of the object [key] in the array [a], (** [bsearch ?cmp key arr] finds the index of the object [key] in the array [arr],
provided [a] is {b sorted} using [cmp]. If the array is not sorted, provided [arr] is {b sorted} using [cmp]. If the array is not sorted,
the result is not specified (may raise Invalid_argument). the result is not specified (may raise Invalid_argument).
Complexity: [O(log n)] where n is the length of the array [a] Complexity: O(log n) where n is the length of the array
(dichotomic search). (dichotomic search).
@return @return
- [`At i] if [cmp a.(i) key = 0] (for some i). - [`At i] if [cmp arr.(i) key = 0] (for some i)
- [`All_lower] if all elements of [a] are lower than [key]. - [`All_lower] if all elements of [arr] are lower than [key]
- [`All_bigger] if all elements of [a] are bigger than [key]. - [`All_bigger] if all elements of [arr] are bigger than [key]
- [`Just_after i] if [a.(i) < key < a.(i+1)]. - [`Just_after i] if [arr.(i) < key < arr.(i+1)]
- [`Empty] if the array [a] is empty. - [`Empty] if the array is empty
@raise Invalid_argument if the array is found to be unsorted w.r.t [cmp]. @raise Invalid_argument if the array is found to be unsorted w.r.t [cmp]
@since 0.13 *) @since 0.13 *)
val for_all2 : f:('a -> 'b -> bool) -> 'a t -> 'b t -> bool val for_all : f:('a -> bool) -> 'a t -> bool
(** [for_all2 ~f [|a1; …; an|] [|b1; …; bn|]] is [true] if each pair of elements [ai bi]
satisfies the predicate [f].
That is, it returns [(f a1 b1) && (f a2 b2) && && (f an bn)].
@raise Invalid_argument if arrays have distinct lengths. val for_all2 : f:('a -> 'b -> bool) -> 'a t -> 'b t -> bool
Allow different types. (** Forall on pairs of arrays.
@since 0.20 *) @raise Invalid_argument if they have distinct lengths
allow different types @since 0.20 *)
val exists : f:('a -> bool) -> 'a t -> bool
val exists2 : f:('a -> 'b -> bool) -> 'a t -> 'b t -> bool val exists2 : f:('a -> 'b -> bool) -> 'a t -> 'b t -> bool
(** [exists2 ~f [|a1; …; an|] [|b1; …; bn|]] is [true] if any pair of elements [ai bi] (** Exists on pairs of arrays.
satisfies the predicate [f]. @raise Invalid_argument if they have distinct lengths
That is, it returns [(f a1 b1) || (f a2 b2) || || (f an bn)]. allow different types @since 0.20 *)
@raise Invalid_argument if arrays have distinct lengths.
Allow different types.
@since 0.20 *)
val fold2 : f:('acc -> 'a -> 'b -> 'acc) -> init:'acc -> 'a t -> 'b t -> 'acc val fold2 : f:('acc -> 'a -> 'b -> 'acc) -> init:'acc -> 'a t -> 'b t -> 'acc
(** [fold2 ~f ~init a b] fold on two arrays [a] and [b] stepwise. (** Fold on two arrays stepwise.
It computes [f ( (f init a1 b1) ) an bn]. @raise Invalid_argument if they have distinct lengths
@raise Invalid_argument if [a] and [b] have distinct lengths.
@since 0.20 *) @since 0.20 *)
val iter2 : f:('a -> 'b -> unit) -> 'a t -> 'b t -> unit val iter2 : f:('a -> 'b -> unit) -> 'a t -> 'b t -> unit
(** [iter2 ~f a b] iterates on the two arrays [a] and [b] stepwise. (** Iterate on two arrays stepwise.
It is equivalent to [f a0 b0; ; f a.(length a - 1) b.(length b - 1); ()]. @raise Invalid_argument if they have distinct lengths
@raise Invalid_argument if [a] and [b] have distinct lengths.
@since 0.20 *) @since 0.20 *)
val shuffle : 'a t -> unit val shuffle : 'a t -> unit
(** [shuffle a] randomly shuffles the array [a], in place. *) (** Shuffle randomly the array, in place *)
val shuffle_with : Random.State.t -> 'a t -> unit val shuffle_with : Random.State.t -> 'a t -> unit
(** [shuffle_with rs a] randomly shuffles the array [a] (like {!shuffle}) but a specialized random (** Like shuffle but using a specialized random state *)
state [rs] is used to control the random numbers being produced during shuffling (for reproducibility). *)
val random_choose : 'a t -> 'a random_gen val random_choose : 'a t -> 'a random_gen
(** [random_choose a rs] randomly chooses an element of [a]. (** Choose an element randomly.
@raise Not_found if the array/slice is empty. *) @raise Not_found if the array/slice is empty *)
val to_string : ?sep:string -> ('a -> string) -> 'a array -> string
(** [to_string ~sep item_to_string a] print [a] to a string using [sep] as a separator
between elements of [a].
@since 2.7 *)
val to_iter : 'a t -> 'a iter
(** [to_iter a] returns an [iter] of the elements of an array [a].
The input array [a] is shared with the sequence and modification of it will result
in modification of the iterator.
@since 2.8 *)
val to_seq : 'a t -> 'a Seq.t
(** [to_seq a] returns a [Seq.t] of the elements of an array [a].
The input array [a] is shared with the sequence and modification of it will result
in modification of the sequence.
Renamed from [to_std_seq] since 3.0.
@since 3.0
*)
val to_seq : 'a t -> 'a sequence
val to_gen : 'a t -> 'a gen val to_gen : 'a t -> 'a gen
(** [to_gen a] returns a [gen] of the elements of an array [a]. *) val to_klist : 'a t -> 'a klist
(** {2 IO} *) (** {2 IO} *)
val pp : val pp: ?sep:string -> 'a printer -> 'a t printer
?pp_start:unit printer -> (** Print an array of items with printing function *)
?pp_stop:unit printer ->
?pp_sep:unit printer ->
'a printer ->
'a t printer
(** [pp ~pp_start ~pp_stop ~pp_sep pp_item ppf a] formats the array [a] on [ppf].
Each element is formatted with [pp_item], [pp_start] is called at the beginning,
[pp_stop] is called at the end, [pp_sep] is called between each elements.
By defaults [pp_start] and [pp_stop] does nothing and [pp_sep] defaults to
(fun out -> Format.fprintf out ",@ "). *)
val pp_i : val pp_i: ?sep:string -> (int -> 'a printer) -> 'a t printer
?pp_start:unit printer -> (** Print an array, giving the printing function both index and item *)
?pp_stop:unit printer ->
?pp_sep:unit printer -> val map : f:('a -> 'b) -> 'a t -> 'b t
(int -> 'a printer) ->
'a t printer
(** [pp_i ~pp_start ~pp_stop ~pp_sep pp_item ppf a] prints the array [a] on [ppf].
The printing function [pp_item] is giving both index and element.
[pp_start] is called at the beginning,
[pp_stop] is called at the end, [pp_sep] is called between each elements.
By defaults [pp_start] and [pp_stop] does nothing and [pp_sep] defaults to
(fun out -> Format.fprintf out ",@ "). *)
val map2 : f:('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t val map2 : f:('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
(** [map2 ~f a b] applies function [f] to all elements of [a] and [b], (** Map on two arrays stepwise.
and builds an array with the results returned by [f]: @raise Invalid_argument if they have distinct lengths
[[| f a.(0) b.(0); ; f a.(length a - 1) b.(length b - 1)|]]. @since 0.20 *)
@raise Invalid_argument if [a] and [b] have distinct lengths.
@since 0.20 *)
val rev : 'a t -> 'a t val rev : 'a t -> 'a t
(** [rev a] copies the array [a] and reverses it in place. (** Copy + reverse in place
@since 0.20 *) @since 0.20 *)
val filter : f:('a -> bool) -> 'a t -> 'a t val filter : f:('a -> bool) -> 'a t -> 'a t
(** [filter ~f a] filters elements out of the array [a]. Only the elements satisfying (** Filter elements out of the array. Only the elements satisfying
the given predicate [f] will be kept. *) the given predicate will be kept. *)
val filter_map : f:('a -> 'b option) -> 'a t -> 'b t val filter_map : f:('a -> 'b option) -> 'a t -> 'b t
(** [filter_map ~f [|a1; …; an|]] calls [(f a1)(f an)] and returns an array [b] consisting (** Map each element into another value, or discard it *)
of all elements [bi] such as [f ai = Some bi]. When [f] returns [None], the corresponding
element of [a] is discarded. *)
val monoid_product : f:('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
(** [monoid_product ~f a b] passes all combinaisons of tuples from the two arrays [a] and [b]
to the function [f].
@since 2.8 *)
val flat_map : f:('a -> 'b t) -> 'a t -> 'b array val flat_map : f:('a -> 'b t) -> 'a t -> 'b array
(** [flat_map ~f a] transforms each element of [a] into an array, then flattens. *) (** Transform each element into an array, then flatten *)
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
(** Infix version of {!flat_map} *)
val (>>|) : 'a t -> ('a -> 'b) -> 'b t
(** Infix version of {!map}
@since 0.8 *)
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
(** Infix version of {!map}
@since 0.8 *)
val except_idx : 'a t -> int -> 'a list val except_idx : 'a t -> int -> 'a list
(** [except_idx a i] removes the element of [a] at given index [i], and returns (** Remove given index, obtaining the list of the other elements *)
the list of the other elements. *)
val (--) : int -> int -> int t
(** Range array *)
val (--^) : int -> int -> int t
(** Range array, excluding right bound
@since 0.17 *)
val random : 'a random_gen -> 'a t random_gen val random : 'a random_gen -> 'a t random_gen
val random_non_empty : 'a random_gen -> 'a t random_gen val random_non_empty : 'a random_gen -> 'a t random_gen
@ -330,48 +217,15 @@ module type MONO_ARRAY = sig
type t type t
val length : t -> int val length : t -> int
val get : t -> int -> elt val get : t -> int -> elt
val set : t -> int -> elt -> unit val set : t -> int -> elt -> unit
end end
val sort_generic : val sort_generic :
(module MONO_ARRAY with type t = 'arr and type elt = 'elt) -> (module MONO_ARRAY with type t = 'arr and type elt = 'elt) ->
cmp:(('elt -> 'elt -> int)[@keep_label]) -> ?cmp:('elt -> 'elt -> int) -> 'arr -> unit
'arr -> (** Sort the array, without allocating (eats stack space though). Performance
unit might be lower than {!Array.sort}.
(** [sort_generic (module M) ~cmp a] sorts the array [a], without allocating (eats stack space though).
Performance might be lower than {!Array.sort}.
@since 0.14 *) @since 0.14 *)
(** {3 Infix Operators}
It is convenient to [open CCArray.Infix] to access the infix operators
without cluttering the scope too much.
@since 2.7 *)
module Infix : sig
val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
(** [a >>= f] is the infix version of {!flat_map}. *)
val ( >>| ) : 'a t -> ('a -> 'b) -> 'b t
(** [a >>| f] is the infix version of {!map}.
@since 0.8 *)
val ( >|= ) : 'a t -> ('a -> 'b) -> 'b t
(** [a >|= f] is the infix version of {!map}.
@since 0.8 *)
val ( -- ) : int -> int -> int t
(** [x -- y] creates an array containing integers in the range [x .. y]. Bounds included. *)
val ( --^ ) : int -> int -> int t
(** [x --^ y] creates an array containing integers in the range [x .. y]. Right bound excluded.
@since 0.17 *)
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
val ( and* ) : 'a t -> 'b t -> ('a * 'b) t
end
include module type of Infix

418
src/core/CCArray_slice.ml Normal file
View file

@ -0,0 +1,418 @@
(* This file is free software, part of containers. See file "license" for more details. *)
(** {1 Array Slice} *)
type 'a sequence = ('a -> unit) -> unit
type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]
type 'a gen = unit -> 'a option
type 'a equal = 'a -> 'a -> bool
type 'a ord = 'a -> 'a -> int
type 'a random_gen = Random.State.t -> 'a
type 'a printer = Format.formatter -> 'a -> unit
(*$inject
let (--) = CCArray.(--)
*)
type 'a t = {
arr : 'a array;
i : int; (** Start index (included) *)
j : int; (** Stop index (excluded) *)
}
let empty = {
arr = [||];
i = 0;
j = 0;
}
let make arr i ~len =
if i<0||i+len > Array.length arr then invalid_arg "Array_slice.make";
{ arr; i; j=i+len; }
let of_slice (arr,i,len) = make arr i ~len
let to_slice a = a.arr, a.i, a.j-a.i
let full arr = { arr; i=0; j=Array.length arr; }
let underlying a = a.arr
let length a = a.j - a.i
let copy a = Array.sub a.arr a.i (length a)
let sub a i len = make a.arr (a.i + i) ~len
(*$=
[ 3;4 ] \
(let a = make (0--10) 2 5 in sub a 1 2 |> to_list)
[ ] \
(let a = make (0--10) 2 5 in sub a 1 0 |> to_list)
[ 5 ] \
(let a = make (0--10) 1 9 in sub a 4 1 |> to_list)
*)
let rec _foldi f acc a i j =
if i = j then acc else _foldi f (f acc i a.(i)) a (i+1) j
let _reverse_in_place a i ~len =
if len=0 then ()
else
for k = 0 to (len-1)/2 do
let t = a.(i+k) in
a.(i+k) <- a.(i+len-1-k);
a.(i+len-1-k) <- t;
done
let rec _equal eq a1 i1 j1 a2 i2 j2 =
if i1 = j1
then (assert (i1=j1 && i2=j2); true)
else
eq a1.(i1) a2.(i2) && _equal eq a1 (i1+1) j1 a2 (i2+1) j2
let rec _compare cmp a1 i1 j1 a2 i2 j2 =
if i1 = j1
then if i2=j2 then 0 else -1
else if i2=j2
then 1
else
let c = cmp a1.(i1) a2.(i2) in
if c = 0
then _compare cmp a1 (i1+1) j1 a2 (i2+1) j2
else c
let equal eq a b =
length a = length b && _equal eq a.arr a.i a.j b.arr b.i b.j
let compare cmp a b =
_compare cmp a.arr a.i a.j b.arr b.i b.j
let fold f acc a =
let rec _fold acc i j =
if i=j then acc
else _fold (f acc a.arr.(i)) (i+1) j
in _fold acc a.i a.j
let to_list a =
let l = fold (fun l x -> x::l) [] a in
List.rev l
let foldi f acc a = _foldi f acc a.arr a.i a.j
let fold_while f acc a =
let rec fold_while_i f acc i =
if i < Array.length a.arr && i < a.j then
let acc, cont = f acc a.arr.(i) in
match cont with
| `Stop -> acc
| `Continue -> fold_while_i f acc (i+1)
else acc
in fold_while_i f acc a.i
let get a i =
let j = a.i + i in
if i<0 || j>=a.j then invalid_arg "Array_slice.get";
a.arr.(j)
let get_safe a i =
try Some (get a i)
with Invalid_argument _ -> None
(*$inject
let sub_a = make [|1;2;3;4;5|] 1 ~len:3
*)
(*$=
(Some 2) (get_safe sub_a 0)
(Some 3) (get_safe sub_a 1)
(Some 4) (get_safe sub_a 2)
None (get_safe sub_a 4)
None (get_safe sub_a max_int)
None (get_safe sub_a ~-1)
None (get_safe sub_a ~-42)
*)
let set a i x =
let j = a.i + i in
if i<0 || j>=a.j then invalid_arg "Array_slice.set";
a.arr.(j) <- x
let iter f a =
for k=a.i to a.j-1 do f a.arr.(k) done
let iteri f a =
for k=0 to length a-1 do f k a.arr.(a.i + k) done
let blit a i b j len =
if i+len>length a || j+len>length b then invalid_arg "Array_slice.blit";
Array.blit a.arr (a.i+i) b.arr (b.i+j) len
let rec _find f a i j =
if i = j then None
else match f i a.(i) with
| Some _ as res -> res
| None -> _find f a (i+1) j
let rec _lookup_rec ~cmp k a i j =
if i>j then raise Not_found
else if i=j
then if cmp k a.(i) = 0
then i
else raise Not_found
else
let middle = (j+i)/2 in
match cmp k a.(middle) with
| 0 -> middle
| n when n<0 -> _lookup_rec ~cmp k a i (middle-1)
| _ -> _lookup_rec ~cmp k a (middle+1) j
let _lookup_exn ~cmp k a i j =
if i>j then raise Not_found;
match cmp k a.(i) with
| 0 -> i
| n when n<0 -> raise Not_found (* too low *)
| _ when i=j -> raise Not_found (* too high *)
| _ ->
match cmp k a.(j) with
| 0 -> j
| n when n<0 -> _lookup_rec ~cmp k a (i+1) (j-1)
| _ -> raise Not_found (* too high *)
let bsearch_ ~cmp x arr i j =
let rec aux i j =
if i > j
then `Just_after j
else
let middle = i + (j - i) / 2 in (* avoid overflow *)
match cmp x arr.(middle) with
| 0 -> `At middle
| n when n<0 -> aux i (middle - 1)
| _ -> aux (middle + 1) j
in
if i>=j then `Empty
else match cmp arr.(i) x, cmp arr.(j) x with
| n, _ when n>0 -> `All_bigger
| _, n when n<0 -> `All_lower
| _ -> aux i j
let rec _for_all p a i j =
i = j || (p a.(i) && _for_all p a (i+1) j)
let rec _exists p a i j =
i <> j && (p a.(i) || _exists p a (i+1) j)
let rec _for_all2 p a1 a2 i1 i2 ~len =
len=0 || (p a1.(i1) a2.(i2) && _for_all2 p a1 a2 (i1+1) (i2+1) ~len:(len-1))
let rec _exists2 p a1 a2 i1 i2 ~len =
len>0 && (p a1.(i1) a2.(i2) || _exists2 p a1 a2 (i1+1) (i2+1) ~len:(len-1))
(* shuffle a[i...j[ using the given int random generator
See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle *)
let _shuffle _rand_int a i j =
for k = j-1 downto i+1 do
let l = _rand_int (k+1) in
let tmp = a.(l) in
a.(l) <- a.(k);
a.(k) <- tmp;
done
(*$T
let st = Random.State.make [||] in let a = 0--10000 in \
let b = Array.copy a in CCArray.shuffle_with st a; a <> b
*)
let _sort_indices cmp a i j =
let len = j-i in
let b = Array.init len (fun k->k) in
Array.sort (fun k1 k2 -> cmp a.(k1+i) a.(k2+i)) b;
b
let _sorted cmp a i j =
let len = j-i in
let b = Array.sub a i len in
Array.sort cmp b;
b
let _choose a i j st =
if i>=j then raise Not_found;
a.(i+Random.State.int st (j-i))
let _pp ~sep pp_item out a i j =
for k = i to j - 1 do
if k > i then (Format.pp_print_string out sep; Format.pp_print_cut out ());
pp_item out a.(k)
done
let _pp_i ~sep pp_item out a i j =
for k = i to j - 1 do
if k > i then (Format.pp_print_string out sep; Format.pp_print_cut out ());
pp_item k out a.(k)
done
let _to_gen a i j =
let k = ref i in
fun () ->
if !k < j
then (
let x = a.(!k) in
incr k;
Some x
) else None
let rec _to_klist a i j () =
if i=j then `Nil else `Cons (a.(i), _to_klist a (i+1) j)
let reverse_in_place a = _reverse_in_place a.arr a.i ~len:(length a)
(*$T
let a = 1--6 in let s = make a 2 ~len:3 in \
reverse_in_place s; a = [| 1; 2; 5; 4; 3; 6 |]
*)
let sorted cmp a = _sorted cmp a.arr a.i a.j
(*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] \
(let a = 1--6 in let s = make a 2 ~len:0 in \
sorted Pervasives.compare s)
[|2;3;4|] \
(let a = [|6;5;4;3;2;1|] in let s = make a 2 ~len:3 in \
sorted Pervasives.compare s)
*)
(*$Q
Q.(array int) (fun a -> \
Array.length a > 10 ==> ( Array.length a > 10 && \
let s = make a 5 ~len:5 in \
let b = Array.sub a 5 5 in \
Array.sort Pervasives.compare b; b = sorted Pervasives.compare s))
*)
let sort_ranking cmp a =
let idx = _sort_indices cmp a.arr a.i a.j in
let cmp_int : int -> int -> int = Pervasives.compare in
let sort_indices cmp a = _sort_indices cmp a 0 (Array.length a) in
sort_indices cmp_int idx
(*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] \
(let a = 1--6 in let s = make a 2 ~len:0 in \
sort_ranking Pervasives.compare s)
[|2;1;3;0|] \
(let a = [|"d";"c";"b";"e";"a"|] in let s = make a 1 ~len:4 in \
sort_ranking Pervasives.compare s)
*)
(*$Q
Q.(array_of_size Gen.(0--50) printable_string) (fun a -> \
Array.length a > 10 ==> ( Array.length a > 10 && \
let s = make a 5 ~len:5 in \
let b = sort_indices String.compare s in \
sorted String.compare s = Array.map (get s) b))
*)
let sort_indices cmp a = _sort_indices cmp a.arr a.i a.j
(*$= & ~cmp:(=) ~printer:Q.Print.(array int)
[||] \
(let a = 1--6 in let s = make a 2 ~len:0 in \
sort_indices Pervasives.compare s)
[|3;1;0;2|] \
(let a = [|"d";"c";"b";"e";"a"|] in let s = make a 1 ~len:4 in \
sort_indices Pervasives.compare s)
*)
(*$Q
Q.(array_of_size Gen.(0--60) printable_string) (fun a -> \
Array.length a > 10 ==> ( Array.length a > 10 && \
let s = make a 5 ~len:5 in \
let b = sort_ranking String.compare s in \
let a_sorted = sorted String.compare s in \
copy s = Array.map (Array.get a_sorted) b))
*)
let find f a = _find (fun _ -> f) a.arr a.i a.j
let findi f a = _find (fun i -> f (i-a.i)) a.arr a.i a.j
let find_idx p a =
_find (fun i x -> if p x then Some (i-a.i,x) else None) a.arr a.i a.j
(*$=
(Some (1,"c")) (find_idx ((=) "c") (make [| "a"; "b"; "c" |] 1 2))
*)
let lookup_exn ?(cmp=Pervasives.compare) k a =
_lookup_exn ~cmp k a.arr a.i (a.j-1) - a.i
let lookup ?(cmp=Pervasives.compare) k a =
try Some (_lookup_exn ~cmp k a.arr a.i (a.j-1) - a.i)
with Not_found -> None
(*$=
(Some 1) (lookup "c" (make [| "a"; "b"; "c" |] 1 2))
*)
let bsearch ?(cmp=Pervasives.compare) k a =
match bsearch_ ~cmp k a.arr a.i (a.j - 1) with
| `At m -> `At (m - a.i)
| `Just_after m -> `Just_after (m - a.i)
| res -> res
let for_all p a = _for_all p a.arr a.i a.j
let exists p a = _exists p a.arr a.i a.j
let for_all2 p a b =
length a = length b && _for_all2 p a.arr b.arr a.i b.i ~len:(length a)
let exists2 p a b =
_exists2 p a.arr b.arr a.i b.i ~len:(min (length a) (length b))
(*$T
exists2 (=) (make [| 1;2;3;4 |] 1 ~len:2) (make [| 0;1;3;4 |] 1 ~len:3)
*)
let _iter2 f a b i j ~len =
for o = 0 to len-1 do
f (Array.get a (i+o)) (Array.get b (j+o))
done
let iter2 f a b =
if length a <> length b then invalid_arg "iter2";
_iter2 f a.arr b.arr a.i b.i ~len:(length a)
let _fold2 f acc a b i j ~len =
let rec aux acc o =
if o=len then acc
else
let acc = f acc (Array.get a (i+o)) (Array.get b (j+o)) in
aux acc (o+1)
in
aux acc 0
let fold2 f acc a b =
if length a <> length b then invalid_arg "fold2";
_fold2 f acc a.arr b.arr a.i b.i ~len:(length a)
let shuffle a =
_shuffle Random.int a.arr a.i a.j
let shuffle_with st a =
_shuffle (Random.State.int st) a.arr a.i a.j
let random_choose a st = _choose a.arr a.i a.j st
let pp ?(sep=", ") pp_item buf a = _pp ~sep pp_item buf a.arr a.i a.j
let pp_i ?(sep=", ") pp_item out a =
_pp_i ~sep (fun k out x -> pp_item (k-a.i) out x) out a.arr a.i a.j
let to_seq a k = iter k a
let to_gen a = _to_gen a.arr a.i a.j
let to_klist a = _to_klist a.arr a.i a.j

Some files were not shown because too many files have changed in this diff Show more