From 8c221fed784b72947c97c27991b1e387f50a81b5 Mon Sep 17 00:00:00 2001 From: Vladan Popovic Date: Sat, 13 Jan 2024 12:19:21 +0100 Subject: [PATCH] first two easy exercises --- .envrc | 4 + dune-project | 23 ++++ flake.lock | 57 ++++++++++ flake.nix | 214 ++++++++++++++++++++++++++++++++++++ hello-world/HELP.md | 36 ++++++ hello-world/Makefile | 9 ++ hello-world/README.md | 42 +++++++ hello-world/dune | 16 +++ hello-world/dune-project | 1 + hello-world/hello_world.ml | 1 + hello-world/hello_world.mli | 4 + hello-world/test.ml | 11 ++ leap/HELP.md | 36 ++++++ leap/Makefile | 9 ++ leap/README.md | 51 +++++++++ leap/dune | 16 +++ leap/dune-project | 1 + leap/leap.ml | 2 + leap/leap.mli | 1 + leap/test.ml | 28 +++++ 20 files changed, 562 insertions(+) create mode 100644 .envrc create mode 100644 dune-project create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hello-world/HELP.md create mode 100644 hello-world/Makefile create mode 100644 hello-world/README.md create mode 100644 hello-world/dune create mode 100644 hello-world/dune-project create mode 100644 hello-world/hello_world.ml create mode 100644 hello-world/hello_world.mli create mode 100644 hello-world/test.ml create mode 100644 leap/HELP.md create mode 100644 leap/Makefile create mode 100644 leap/README.md create mode 100644 leap/dune create mode 100644 leap/dune-project create mode 100644 leap/leap.ml create mode 100644 leap/leap.mli create mode 100644 leap/test.ml diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1e71ffb --- /dev/null +++ b/.envrc @@ -0,0 +1,4 @@ +# shellcheck shell=bash +# For use with direnv. +# Installing nix-direnv will ensure a smoother experience. +use flake \ No newline at end of file diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..4215236 --- /dev/null +++ b/dune-project @@ -0,0 +1,23 @@ +(lang dune 3.2) + +(name exercism) + +(generate_opam_files false) + +(source + (uri https://gitea.vp.mk/vladan/exercism)) + +(authors "Vladan Popovic") +(maintainers "Vladan Popovic") + +(license LICENSE) + +(package + (name exercism) + (synopsis "Exercism OCaml exercises") + (description "Exercism OCaml exercises") + (depends ocaml dune) + (tags + (exercism "Exercism OCaml exercises"))) + +; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..549ba2e --- /dev/null +++ b/flake.lock @@ -0,0 +1,57 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nix-filter": { + "locked": { + "lastModified": 1666547822, + "narHash": "sha256-razwnAybPHyoAyhkKCwXdxihIqJi1G6e1XP4FQOJTEs=", + "owner": "numtide", + "repo": "nix-filter", + "rev": "1a3b735e13e90a8d2fd5629f2f8363bd7ffbbec7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "nix-filter", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1669867399, + "narHash": "sha256-Z8RXSFYOsIsTG96ROKtV0eZ8Q7u4irFWm6ELqfw7mT8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "38e591dd05ffc8bdf79dc752ba78b05e370416fa", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nix-filter": "nix-filter", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9c81982 --- /dev/null +++ b/flake.nix @@ -0,0 +1,214 @@ +{ + description = "A flake demonstrating how to build OCaml projects with Dune"; + + # Flake dependency specification + # + # To update all flake inputs: + # + # $ nix flake update --commit-lockfile + # + # To update individual flake inputs: + # + # $ nix flake lock --update-input ... --commit-lockfile + # + inputs = { + # Convenience functions for writing flakes + flake-utils.url = "github:numtide/flake-utils"; + # Precisely filter files copied to the nix store + nix-filter.url = "github:numtide/nix-filter"; + }; + + outputs = { self, nixpkgs, flake-utils, nix-filter }: + # Construct an output set that supports a number of default systems + flake-utils.lib.eachDefaultSystem (system: + let + # Legacy packages that have not been converted to flakes + legacyPackages = nixpkgs.legacyPackages.${system}; + # OCaml packages available on nixpkgs + ocamlPackages = legacyPackages.ocamlPackages; + # Library functions from nixpkgs + lib = legacyPackages.lib; + + # Filtered sources (prevents unecessary rebuilds) + sources = { + ocaml = nix-filter.lib { + root = ./.; + include = [ + ".ocamlformat" + "dune-project" + (nix-filter.lib.inDirectory "bin") + (nix-filter.lib.inDirectory "lib") + (nix-filter.lib.inDirectory "test") + ]; + }; + + nix = nix-filter.lib { + root = ./.; + include = [ + (nix-filter.lib.matchExt "nix") + ]; + }; + }; + in + { + # Exposed packages that can be built or run with `nix build` or + # `nix run` respectively: + # + # $ nix build .# + # $ nix run .# -- + # + packages = { + # The package that will be built or run by default. For example: + # + # $ nix build + # $ nix run -- + # + default = self.packages.${system}.exercism; + + exercism = ocamlPackages.buildDunePackage { + pname = "exercism"; + version = "0.1.0"; + duneVersion = "3"; + src = sources.ocaml; + + strictDeps = true; + + preBuild = '' + dune build exercism.opam + ''; + }; + }; + + # Flake checks + # + # $ nix flake check + # + checks = { + # Run tests for the `exercism` package + exercism = + let + # Patches calls to dune commands to produce log-friendly output + # when using `nix ... --print-build-log`. Ideally there would be + # support for one or more of the following: + # + # In Dune: + # + # - have workspace-specific dune configuration files + # + # In NixPkgs: + # + # - allow dune flags to be set in in `ocamlPackages.buildDunePackage` + # - alter `ocamlPackages.buildDunePackage` to use `--display=short` + # - alter `ocamlPackages.buildDunePackage` to allow `--config-file=FILE` to be set + patchDuneCommand = + let + subcmds = [ "build" "test" "runtest" "install" ]; + in + lib.replaceStrings + (lib.lists.map (subcmd: "dune ${subcmd}") subcmds) + (lib.lists.map (subcmd: "dune ${subcmd} --display=short") subcmds); + in + + self.packages.${system}.exercism.overrideAttrs + (oldAttrs: { + name = "check-${oldAttrs.name}"; + doCheck = true; + buildPhase = patchDuneCommand oldAttrs.buildPhase; + checkPhase = patchDuneCommand oldAttrs.checkPhase; + # skip installation (this will be tested in the `exercism-app` check) + installPhase = "touch $out"; + }); + + # Check Dune and OCaml formatting + dune-fmt = legacyPackages.runCommand "check-dune-fmt" + { + nativeBuildInputs = [ + ocamlPackages.dune_3 + ocamlPackages.ocaml + legacyPackages.ocamlformat + ]; + } + '' + echo "checking dune and ocaml formatting" + dune build \ + --display=short \ + --no-print-directory \ + --root="${sources.ocaml}" \ + --build-dir="$(pwd)/_build" \ + @fmt + touch $out + ''; + + # Check documentation generation + dune-doc = legacyPackages.runCommand "check-dune-doc" + { + ODOC_WARN_ERROR = "true"; + nativeBuildInputs = [ + ocamlPackages.dune_3 + ocamlPackages.ocaml + ocamlPackages.odoc + ]; + } + '' + echo "checking ocaml documentation" + dune build \ + --display=short \ + --no-print-directory \ + --root="${sources.ocaml}" \ + --build-dir="$(pwd)/_build" \ + @doc + touch $out + ''; + + # Check Nix formatting + nixpkgs-fmt = legacyPackages.runCommand "check-nixpkgs-fmt" + { nativeBuildInputs = [ legacyPackages.nixpkgs-fmt ]; } + '' + echo "checking nix formatting" + nixpkgs-fmt --check ${sources.nix} + touch $out + ''; + }; + + # Development shells + # + # $ nix develop .# + # $ nix develop .# --command dune build @test + # + # [Direnv](https://direnv.net/) is recommended for automatically loading + # development environments in your shell. For example: + # + # $ echo "use flake" > .envrc && direnv allow + # $ dune build @test + # + devShells = { + default = legacyPackages.mkShell { + # Development tools + packages = [ + # Source file formatting + legacyPackages.nixpkgs-fmt + legacyPackages.ocamlformat + # For `dune build --watch ...` + legacyPackages.fswatch + # For `dune build @doc` + ocamlPackages.odoc + # OCaml editor support + ocamlPackages.ocaml-lsp + # Nicely formatted types on hover + ocamlPackages.ocamlformat-rpc-lib + # Fancy REPL thing + ocamlPackages.utop + ocamlPackages.angstrom + ocamlPackages.ounit + ocamlPackages.ounit2 + ocamlPackages.ppx_sexp_conv + ]; + + # Tools from packages + inputsFrom = [ + self.packages.${system}.exercism + ]; + }; + }; + }); +} diff --git a/hello-world/HELP.md b/hello-world/HELP.md new file mode 100644 index 0000000..124fb3c --- /dev/null +++ b/hello-world/HELP.md @@ -0,0 +1,36 @@ +# Help + +## Running the tests + +A `Makefile` is provided with a default target to compile your solution and run the tests. At the command line in your exercise's directory, type: + +```bash +make +``` + +## Submitting your solution + +You can submit your solution using the `exercism submit hello_world.ml` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [OCaml track's documentation](https://exercism.org/docs/tracks/ocaml) +- The [OCaml track's programming category on the forum](https://forum.exercism.org/c/programming/ocaml) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +To get help if you're having trouble, you can use one of the following resources: + +- [Documentation for the Standard Library](http://caml.inria.fr/pub/docs/manual-ocaml/libref/index.html) +- [/r/ocaml](https://www.reddit.com/r/ocaml) is the OCaml subreddit. +- [StackOverflow](http://stackoverflow.com/questions/tagged/ocaml) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. \ No newline at end of file diff --git a/hello-world/Makefile b/hello-world/Makefile new file mode 100644 index 0000000..b71d6af --- /dev/null +++ b/hello-world/Makefile @@ -0,0 +1,9 @@ +default: clean test + +test: + dune runtest + +clean: + dune clean + +.PHONY: clean diff --git a/hello-world/README.md b/hello-world/README.md new file mode 100644 index 0000000..89de4e3 --- /dev/null +++ b/hello-world/README.md @@ -0,0 +1,42 @@ +# Hello World + +Welcome to Hello World on Exercism's OCaml Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +The classical introductory exercise. +Just say "Hello, World!". + +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. + +The objectives are simple: + +- Modify the provided code so that it produces the string "Hello, World!". +- Run the test suite and make sure that it succeeds. +- Submit your solution and check it at the website. + +If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program + +## Source + +### Created by + +- @dvberkel + +### Contributed to by + +- @daveyarwood +- @iHiD +- @kytrinyx +- @marionebl +- @Peaupote +- @sbl +- @sshine +- @stevejb71 + +### Based on + +This is an exercise to introduce users to using Exercism - https://en.wikipedia.org/wiki/%22Hello,_world!%22_program \ No newline at end of file diff --git a/hello-world/dune b/hello-world/dune new file mode 100644 index 0000000..28aeac0 --- /dev/null +++ b/hello-world/dune @@ -0,0 +1,16 @@ +(executable + (name test) + (libraries base ounit2)) + +(alias + (name runtest) + (deps (:x test.exe)) + (action (run %{x}))) + +(alias + (name buildtest) + (deps (:x test.exe))) + +(env + (dev + (flags (:standard -warn-error -A)))) \ No newline at end of file diff --git a/hello-world/dune-project b/hello-world/dune-project new file mode 100644 index 0000000..7655de0 --- /dev/null +++ b/hello-world/dune-project @@ -0,0 +1 @@ +(lang dune 1.1) diff --git a/hello-world/hello_world.ml b/hello-world/hello_world.ml new file mode 100644 index 0000000..084ff50 --- /dev/null +++ b/hello-world/hello_world.ml @@ -0,0 +1 @@ +let hello = "Hello, World!" diff --git a/hello-world/hello_world.mli b/hello-world/hello_world.mli new file mode 100644 index 0000000..15597a4 --- /dev/null +++ b/hello-world/hello_world.mli @@ -0,0 +1,4 @@ +(* + Returns "Hello, World!" +*) +val hello: string diff --git a/hello-world/test.ml b/hello-world/test.ml new file mode 100644 index 0000000..0285a2f --- /dev/null +++ b/hello-world/test.ml @@ -0,0 +1,11 @@ +open OUnit2 +open Hello_world + +let ae exp got _test_ctxt = assert_equal ~printer:(fun x -> x) exp got + +let tests = [ + "Say Hi!" >:: ae "Hello, World!" hello; +] + +let () = + run_test_tt_main ("Hello World tests" >::: tests) diff --git a/leap/HELP.md b/leap/HELP.md new file mode 100644 index 0000000..f08d2dc --- /dev/null +++ b/leap/HELP.md @@ -0,0 +1,36 @@ +# Help + +## Running the tests + +A `Makefile` is provided with a default target to compile your solution and run the tests. At the command line in your exercise's directory, type: + +```bash +make +``` + +## Submitting your solution + +You can submit your solution using the `exercism submit leap.ml` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [OCaml track's documentation](https://exercism.org/docs/tracks/ocaml) +- The [OCaml track's programming category on the forum](https://forum.exercism.org/c/programming/ocaml) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +To get help if you're having trouble, you can use one of the following resources: + +- [Documentation for the Standard Library](http://caml.inria.fr/pub/docs/manual-ocaml/libref/index.html) +- [/r/ocaml](https://www.reddit.com/r/ocaml) is the OCaml subreddit. +- [StackOverflow](http://stackoverflow.com/questions/tagged/ocaml) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. \ No newline at end of file diff --git a/leap/Makefile b/leap/Makefile new file mode 100644 index 0000000..b71d6af --- /dev/null +++ b/leap/Makefile @@ -0,0 +1,9 @@ +default: clean test + +test: + dune runtest + +clean: + dune clean + +.PHONY: clean diff --git a/leap/README.md b/leap/README.md new file mode 100644 index 0000000..d8d520e --- /dev/null +++ b/leap/README.md @@ -0,0 +1,51 @@ +# Leap + +Welcome to Leap on Exercism's OCaml Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Given a year, report if it is a leap year. + +The tricky thing here is that a leap year in the Gregorian calendar occurs: + +```text +on every year that is evenly divisible by 4 + except every year that is evenly divisible by 100 + unless the year is also evenly divisible by 400 +``` + +For example, 1997 is not a leap year, but 1996 is. +1900 is not a leap year, but 2000 is. + +## Notes + +Though our exercise adopts some very simple rules, there is more to learn! + +For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. + +[video]: https://www.youtube.com/watch?v=xX96xng7sAE + +## Source + +### Created by + +- @tmcgilchrist + +### Contributed to by + +- @daveyarwood +- @dvberkel +- @iHiD +- @ismaelga +- @kytrinyx +- @marionebl +- @Peaupote +- @pminten +- @sbl +- @sshine +- @stevejb71 + +### Based on + +CodeRanch Cattle Drive, Assignment 3 - https://coderanch.com/t/718816/Leap \ No newline at end of file diff --git a/leap/dune b/leap/dune new file mode 100644 index 0000000..28aeac0 --- /dev/null +++ b/leap/dune @@ -0,0 +1,16 @@ +(executable + (name test) + (libraries base ounit2)) + +(alias + (name runtest) + (deps (:x test.exe)) + (action (run %{x}))) + +(alias + (name buildtest) + (deps (:x test.exe))) + +(env + (dev + (flags (:standard -warn-error -A)))) \ No newline at end of file diff --git a/leap/dune-project b/leap/dune-project new file mode 100644 index 0000000..7655de0 --- /dev/null +++ b/leap/dune-project @@ -0,0 +1 @@ +(lang dune 1.1) diff --git a/leap/leap.ml b/leap/leap.ml new file mode 100644 index 0000000..f821830 --- /dev/null +++ b/leap/leap.ml @@ -0,0 +1,2 @@ +let leap_year year = + (year mod 4 = 0 && year mod 100 <> 0) || year mod 400 = 0 diff --git a/leap/leap.mli b/leap/leap.mli new file mode 100644 index 0000000..9e987f3 --- /dev/null +++ b/leap/leap.mli @@ -0,0 +1 @@ +val leap_year: int -> bool diff --git a/leap/test.ml b/leap/test.ml new file mode 100644 index 0000000..64d7120 --- /dev/null +++ b/leap/test.ml @@ -0,0 +1,28 @@ +open OUnit2 +open Leap + +let ae exp got _test_ctxt = assert_equal exp got ~printer:string_of_bool + +let tests = [ + "year not divisible by 4 in common year" >:: + ae false (leap_year 2015); + "year divisible by 2, not divisible by 4 in common year" >:: + ae false (leap_year 1970); + "year divisible by 4, not divisible by 100 in leap year" >:: + ae true (leap_year 1996); + "year divisible by 4 and 5 is still a leap year" >:: + ae true (leap_year 1960); + "year divisible by 100, not divisible by 400 in common year" >:: + ae false (leap_year 2100); + "year divisible by 100 but not by 3 is still not a leap year" >:: + ae false (leap_year 1900); + "year divisible by 400 is leap year" >:: + ae true (leap_year 2000); + "year divisible by 400 but not by 125 is still a leap year" >:: + ae true (leap_year 2400); + "year divisible by 200, not divisible by 400 in common year" >:: + ae false (leap_year 1800); +] + +let () = + run_test_tt_main ("leap tests" >::: tests)