From 44aa4860954dce3a5bae34d969c4b54de80b0022 Mon Sep 17 00:00:00 2001 From: G-J van Rooyen Date: Mon, 2 Sep 2024 20:02:06 +0200 Subject: [PATCH] Tooling and CI set up + 1st four exercises (#25) * hello-word, leap, configuration updates Completed the hello-world and leap exercises. Updated configuration. * Add workflow in test.yml * Modified CI workflow to build Odin from source * Completed difference-of-squares, added format-all Completed the difference-of-squares practice exercise, and added the `bin/format-all.sh` script that recursively searches for .odin files and runs `odinfmt` on them. * Formatted source, added formatting to webhook All source files have been reformatted with the `odinfmt.json` spec. `bin/format-all.sh` is now run automatically on commit. * Added step to push autoformatting changes The `test.yml` GitHub workflow now pushes any changes that it makes, so that they persist in the repo. * Modified test.yml to not fail on no formatting `test.yml` now only attempts a commit and push if the automated formatting actually changed any files. * Added missing dashes in argument * Fixed bin/verify-exercises The verify-exercises script now runs the individual test for both concept and practice exercises, and is the default in the GitHub workflow. * Added the "grains" exercise Tests were lifted from the Gleam track. * Updated README; fixed test verification - The README now contains more thorough instructions on how to add a new exercise. - Fixed a variable name in the test runner that caused the "test all solutions" loop to run multiple times when verifying solutions. * Formatted config files with configlet * Fixed testing bug that clobbered stub solutions - Fixed bin/run-test.sh so that it doesn't delete the stub solution when a test fails. - Restored exercises, and added a NotImplemented error condition for the stubs. * Small code fixes. Updated README. - Ran the code formatter on source files. - Rehaul of the README to follow Python's example. * Added resistor-color exercise. Updated README. * Fixed and tested the test.yml action The workflow now uses a fixed commit hash to pull a specific release of Odin. We now use Ubuntu 22.04 which has the libffi8 dependency, and add clang as the only other dependency. * Implemented the resistor-color exercise * Removed configlet dependency in test.yml * Moved the exercise checklist to a linked GH issue * Test runner now checks that stub solution fails Added a check to bin/run-test.sh so that verifies that the stub solution does *not* pass any tests. Fixed hello-world.odin to actually be a stub. * Fixed incorrect stubs, added panics for stub procs - Improved bin/run-test.sh to add more visible color coding to the output. - Replaced NotImplemented error codes with a compile-time panic for procedures that have not yet been implemented. - Fixed stubs that were actually complete examples. * Removed fancy terminal color commands * Added support for skipping and unskipping tests - Exercises' unit tests were modified so that only a minimal subset of tests are initially enabled; the rest are skipped. - The test runner was modified to automatically unskip all test cases before running the tests. * Removed mention of NotImplemented in the README --- .github/workflows/test.yml | 37 ++- README.md | 210 +++++++++--------- bin/fetch-ols-odinfmt.sh | 4 +- bin/format-all.sh | 6 + bin/gen-exercise.sh | 6 +- bin/run-test.sh | 61 +++-- bin/verify-exercises | 6 +- config.json | 54 ++++- .../.docs/instructions.md | 14 ++ .../difference-of-squares/.meta/config.json | 19 ++ .../.meta/difference_of_squares_example.odin | 14 ++ .../difference-of-squares/.meta/tests.toml | 37 +++ .../difference_of_squares.odin | 13 ++ .../difference_of_squares_test.odin | 53 +++++ .../practice/grains/.docs/instructions.md | 15 ++ exercises/practice/grains/.meta/config.json | 19 ++ .../practice/grains/.meta/grains_example.odin | 35 +++ exercises/practice/grains/.meta/tests.toml | 43 ++++ exercises/practice/grains/grains.odin | 14 ++ exercises/practice/grains/grains_test.odin | 105 +++++++++ .../hello-world/.docs/instructions.md | 16 ++ .../practice/hello-world/.meta/config.json | 19 ++ .../.meta/hello_world_example.odin | 5 + .../practice/hello-world/.meta/tests.toml | 13 ++ .../practice/hello-world/hello_world.odin | 5 + .../hello-world/hello_world_test.odin | 10 + exercises/practice/leap/.meta/config.json | 16 +- .../practice/leap/.meta/leap_example.odin | 5 + exercises/practice/leap/.meta/tests.toml | 37 +++ exercises/practice/leap/leap.odin | 5 + exercises/practice/leap/leap_test.odin | 61 +++++ .../resistor-color/.docs/instructions.md | 39 ++++ .../practice/resistor-color/.meta/config.json | 19 ++ .../.meta/resistor_color_example.odin | 33 +++ .../practice/resistor-color/.meta/tests.toml | 22 ++ .../resistor-color/resistor_color.odin | 12 + .../resistor-color/resistor_color_test.odin | 79 +++++++ ols.json | 3 +- src/odin_code_generator.odin | 185 ++++++++------- 39 files changed, 1104 insertions(+), 245 deletions(-) create mode 100755 bin/format-all.sh create mode 100644 exercises/practice/difference-of-squares/.docs/instructions.md create mode 100644 exercises/practice/difference-of-squares/.meta/config.json create mode 100644 exercises/practice/difference-of-squares/.meta/difference_of_squares_example.odin create mode 100644 exercises/practice/difference-of-squares/.meta/tests.toml create mode 100644 exercises/practice/difference-of-squares/difference_of_squares.odin create mode 100644 exercises/practice/difference-of-squares/difference_of_squares_test.odin create mode 100644 exercises/practice/grains/.docs/instructions.md create mode 100644 exercises/practice/grains/.meta/config.json create mode 100644 exercises/practice/grains/.meta/grains_example.odin create mode 100644 exercises/practice/grains/.meta/tests.toml create mode 100644 exercises/practice/grains/grains.odin create mode 100644 exercises/practice/grains/grains_test.odin create mode 100644 exercises/practice/hello-world/.docs/instructions.md create mode 100644 exercises/practice/hello-world/.meta/config.json create mode 100644 exercises/practice/hello-world/.meta/hello_world_example.odin create mode 100644 exercises/practice/hello-world/.meta/tests.toml create mode 100644 exercises/practice/hello-world/hello_world.odin create mode 100644 exercises/practice/hello-world/hello_world_test.odin create mode 100644 exercises/practice/leap/.meta/leap_example.odin create mode 100644 exercises/practice/leap/.meta/tests.toml create mode 100644 exercises/practice/leap/leap.odin create mode 100644 exercises/practice/leap/leap_test.odin create mode 100644 exercises/practice/resistor-color/.docs/instructions.md create mode 100644 exercises/practice/resistor-color/.meta/config.json create mode 100644 exercises/practice/resistor-color/.meta/resistor_color_example.odin create mode 100644 exercises/practice/resistor-color/.meta/tests.toml create mode 100644 exercises/practice/resistor-color/resistor_color.odin create mode 100644 exercises/practice/resistor-color/resistor_color_test.odin diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df75aba..b90c5c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,19 +1,6 @@ -# This workflow will do a clean install of the dependencies and run tests across different versions -# -# Replace with the track name -# Replace with an image to run the jobs on -# Replace with a github action to setup tooling on the image -# Replace with a cli command to install the dependencies -# -# Find Github Actions to setup tooling here: -# - https://github.com/actions/?q=setup&type=&language= -# - https://github.com/actions/starter-workflows/tree/main/ci -# - https://github.com/marketplace?type=actions&query=setup -# -# Requires scripts: -# - bin/test - -name: / Test +# This workflow creates an Odin environment and runs all tests for all exercises. + +name: odin/Test on: push: @@ -23,17 +10,23 @@ on: jobs: ci: - runs-on: + runs-on: ubuntu-22.04 steps: + - name: Update packages + run: sudo apt update + + - name: Install Clang + run: sudo apt -y install clang + - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - name: Use - uses: - - - name: Install project dependencies - run: + - name: Setup Odin + uses: laytan/setup-odin@41f9612bfec760bbb68b05b5747f319afe7c48d8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + llvm-version: 14 - name: Verify all exercises run: bin/verify-exercises diff --git a/README.md b/README.md index 63ffcdb..99814fe 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,108 @@ # Odin -Official Exercism forum thread about this track: https://forum.exercism.org/t/new-track-odin-programming-language/7379 - -Borring concepts from other C-based/C-adjacent language tracks: -- https://github.com/exercism/c -- https://github.com/exercism/zig - -## TODO - -- Figure out how to build an Odin test runner (currently using bash script for this) -- [Highlight.js support for Odin](https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md) - -## Odin Docs - -- [Odin website](http://odin-lang.org) -- [Odin GitHub](https://github.com/odin-lang/Odin) -- [Odin examples](https://github.com/odin-lang/examples) -- [Odin language server](https://github.com/DanielGavin/ols) - -## Exercism Docs - -- https://exercism.org/docs/building/tracks/new/request-new -- https://exercism.org/docs/building/tracks/new/add-first-exercise -- https://exercism.org/docs/building/tracks/new/add-initial-exercises -- https://exercism.org/docs/building/tracks/new/setup-continuous-integration -- https://exercism.org/docs/building/tooling/test-runners -- https://github.com/exercism/generic-track -- https://github.com/exercism/problem-specifications - -## Example Nix Config - -```nix -{ pkgs }: -let - inherit (pkgs) lib; - - # TODO: Building odinfmt requires the nighly build of Odin itself - new_pkgs = import - (pkgs.fetchFromGitHub { - owner = "NixOS"; - repo = "nixpkgs"; - rev = "ef66aec42b5f9035a675496e9a7fe57b63505646"; - # sha256 = lib.fakeSha256; - sha256 = "1j1ywwk1wzcd60dbam3pif8z0v695ssmm8g4aw9j01wl36pds31a"; - }) - { }; - - odin = new_pkgs.odin; -in -{ - deps = [ - odin - pkgs.ruby - pkgs.gh - pkgs.just - pkgs.jq - ]; -} -``` - -*Below is the previous generic track readme; will modify later.* - ---- - -# Exercism Odin Track - -[![Configlet](https://github.com/exercism/odin/actions/workflows/configlet.yml/badge.svg)](https://github.com/exercism/odin/actions/workflows/configlet.yml) [![.github/workflows/test.yml](https://github.com/exercism/odin/actions/workflows/test.yml/badge.svg)](https://github.com/exercism/odin/actions/workflows/test.yml) - -Exercism exercises in Odin. - -## Testing - -To test the exercises, run `./bin/test`. -This command will iterate over all exercises and check to see if their exemplar/example implementation passes all the tests. - -### Track linting - -[`configlet`](https://exercism.org/docs/building/configlet) is an Exercism-wide tool for working with tracks. You can download it by running: - -```shell -./bin/fetch-configlet -``` - -Run its [`lint` command](https://exercism.org/docs/building/configlet/lint) to verify if all exercises have all the necessary files and if config files are correct: - -```shell -$ ./bin/configlet lint - -The lint command is under development. -Please re-run this command regularly to see if your track passes the latest linting rules. - -Basic linting finished successfully: -- config.json exists and is valid JSON -- config.json has these valid fields: - language, slug, active, blurb, version, status, online_editor, key_features, tags -- Every concept has the required .md files -- Every concept has a valid links.json file -- Every concept has a valid .meta/config.json file -- Every concept exercise has the required .md files -- Every concept exercise has a valid .meta/config.json file -- Every practice exercise has the required .md files -- Every practice exercise has a valid .meta/config.json file -- Required track docs are present -- Required shared exercise docs are present -``` +
+ +Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 + +
+ +**`exercism/odin`** is one of many programming language tracks on [Exercism](exercism-website). +This repo holds all the instructions, tests, code, and support files for Odin _exercises_ currently under development or implemented and available for students. + +🌟   Track exercises support the `dev-2024-08` release of Odin. + +Exercises are grouped into **concept** exercises which teach the Odin syllabus, which will eventually live [here][odin-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴 . +Concept exercises are constrained to a small set of language or syntax features. +Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `odin/exercises` directory. + +

+ +
+ + + + + +🌟🌟  Please take a moment to read our [Code of Conduct][exercism-code-of-conduct] 🌟🌟  +It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use]. + +                         Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins] + +
+ +
+ + +Here to suggest a new feature or new exercise?? **Hooray!**  🎉   +We'd love if you did that via our [Exercism Community Forum][exercism-forum] where there is a [dedicated thread][odin-thread] for the new Odin track. +Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence]. +_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._ + +
+ + +✨ 🦄  _**Want to jump directly into Exercism specifications & detail?**_ +     [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation] +     [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_) + +
+
+ +## Contributing an Exercise +If you are interested in contributing a new exercise, please have a look at [this issue][odin-backlog] to see which exercises are waiting on implementation. +Leave a comment in the issue to notify other contributors which exercise you plan to implement. + +The `bin/` subdirectory contains several scripts to help you contribute exercises that will run correctly on Exercism: + +- `configlet` is a tool to help track maintainers with the maintenance of their track. + Fetch it by running the `bin/fetch-configlet` script. + Run `bin/configlet lint` to verify that the track is properly structured. +- `bin/fetch-ols-odinfmt.sh` will fetch the Odin language server (`ols`) that can assist with verifying Odin code directly in your IDE. + `odinfmt` is a tool that can format Odin code according to the specification in `odinfmt.json`. + Please run `odinfmt` before pushing your changes to the repository. + whenever new code is pushed to the repository. +- `bin/format-all.sh` will run `odinfmt` on all `.odin` files in the repository. +- `bin/run-test.sh` runs the tests for a specific exercise, or for all exercises if no exercise name is provided. +- `bin/verify-exercises` checks the integrity of all exercises, including tests. + It is used by the build system whenever new code is pushed to the repository. +- `bin/configlet` can be used to generate a new exercise. More details follow below. + +### Creating a New Exercise +- Run `bin/configlet create --practice-exercise ` to automatically generate the exercise skeleton in the `exercises/practice//` directory and to update `config.json` to reference the new exercise. + You can add `--author ` as option to mark yourself as the creator of this exercise (or add it later in the exercise's `.meta/config.json` file.) +- Add a solution stub at the exercise's `.odin` file. + This is what students will begin with when they start the exercise. + It should make it as easy as possible to understand what they need to solve, without revealing too much of the solution. + Stub functions should usually panic, e.g. `#panic("Please implement the function.")`. +- Add tests to `_test.odin`. + Verify that the slug solution would fail _all_ tests. +- Implement a reference solution at `.meta/_example.odin`. +- Use `bin/run_test.sh ` to verify that your reference solution passes. + +[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member +[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md +[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md +[config-json]: https://github.com/exercism/odin/blob/main/config.json +[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md +[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md +[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct +[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md +[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md +[exercism-forum]: https://forum.exercism.org/ +[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md +[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring +[exercism-tasks]: https://exercism.org/docs/building/product/tasks +[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md +[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks +[exercism-website]: https://exercism.org/ +[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md +[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers +[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md +[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md +[odin-backlog]: https://github.com/exercism/odin/issues/26 +[odin-release]: https://github.com/odin-lang/Odin/releases/tag/dev-2024-08 +[odin-syllabus]: https://exercism.org/tracks/odin/concepts +[odin-thread]: https://forum.exercism.org/t/new-track-odin-programming-language/7379 +[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md +[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md +[website-contributing-section]: https://exercism.org/docs/building diff --git a/bin/fetch-ols-odinfmt.sh b/bin/fetch-ols-odinfmt.sh index d0bda42..4b20857 100755 --- a/bin/fetch-ols-odinfmt.sh +++ b/bin/fetch-ols-odinfmt.sh @@ -1,7 +1,7 @@ #!/bin/bash # version="refs/heads/master" -version="46892948312c14b44600ae9f557e86bd8c792343" +version="e2f4f96cd46b70360f3caa58acc4af14eb0e8688" bin_dir="bin" name="ols" @@ -35,4 +35,4 @@ mv odinfmt .. popd > /dev/null rm -rf $tarball_dir -rm -f $tarball_path \ No newline at end of file +rm -f $tarball_path diff --git a/bin/format-all.sh b/bin/format-all.sh new file mode 100755 index 0000000..cc498e0 --- /dev/null +++ b/bin/format-all.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Runs `odinfmt` on all .odin files in the current tree. + +find . -type f -name "*.odin" -exec odinfmt -w {} \; +echo "All Odin files have been formatted." diff --git a/bin/gen-exercise.sh b/bin/gen-exercise.sh index e7c8fac..9b84a72 100755 --- a/bin/gen-exercise.sh +++ b/bin/gen-exercise.sh @@ -99,8 +99,8 @@ EOL # Make the example file a simple copy of the solution file cp ${solution_file} ${example_file} - echo "Formatting new Odin files:" - bin/odinfmt -w ${exercises_path} + # echo "Formatting new Odin files:" + # bin/odinfmt -w ${exercises_path} echo "Be sure to implement the following files:" echo -e "\t${solution_file}" @@ -110,4 +110,4 @@ EOL echo "Running configlet lint:" bin/configlet lint -fi \ No newline at end of file +fi diff --git a/bin/run-test.sh b/bin/run-test.sh index 5ec7cd5..53d94de 100755 --- a/bin/run-test.sh +++ b/bin/run-test.sh @@ -15,9 +15,12 @@ function run_test() { meta=".meta" exercise_name="${1}" exercise_path="${exercises_path}/${exercise_name}" + tmp_path=`mktemp -d` + + echo "$exercise_name / $exercise_path" if [ -n "${exercise_name}" ] && [ -d "${exercise_path}" ]; then - echo "Running test for exercise: ${exercise_name}" + echo -e "Running test for exercise: ${exercise_name}\n" # Turn something like "hello-world" into "hello_world" exercise_safe_name=$(echo $exercise_name | to_snake_case) @@ -31,20 +34,42 @@ function run_test() { # "exercises/practice/.meta/hello_world_example.odin" example_file="${exercise_path}/${meta}/${exercise_safe_name}_example.odin" - # Move the blank solution file into the meta directory for a bit - mv ${solution_file} ${exercise_path}/${meta} - - # Copy the example file into the main directory - cp ${example_file} ${solution_file} - - # Run the tests using the example file - odin test ${exercise_path} + # Copy the example file into the temporary directory + cp ${example_file} ${tmp_path}/${exercise_safe_name}.odin + + # Unskip all tests and write the processed test file to the temporary directory. + # The test file for the exercise often has several of the tests skippped initially, so that + # students can do test-driven development by enabling the next test, possibly see it fail, + # and then refining their solution. However, the test runner used by contributors and the CI + # pipeline always needs to run all tests. + # + # In Odin, a test can be skipped by commenting out the `@(test)` annotation preceding the + # test procedure. Here we unskip the test by searching for `\\ @(test)` lines and replacing + # them with `@test`. + sed s/"\/\/ @(test)"/"@(test)"/ ${test_file} > ${tmp_path}/${exercise_safe_name}_test.odin - # Move the blank solution file back into the main directory - mv "${exercise_path}/${meta}/${exercise_safe_name}.odin" ${solution_file} + # Run the tests using the example file to verify that it is a valid solution. + odin test ${tmp_path} + + echo -e "Checking that the stub solution *fails* the tests\n" + + # Copy the stub solution to the temporary directory + cp ${solution_file} ${tmp_path}/${exercise_safe_name}.odin + + # Run the test. If it passes, exit with a message and an error. + # TODO: Check that the stub fails _all_ the tests. + # We only check for a single failed test here -- the stub solution could solve all the cases + # but only fail on the most complicated one. Since the purpose of this test is mostly to + # double-check that the example didn't accidentally get duplicated as the stub, this isn't + # too critical for now. + + if odin test ${tmp_path} ; then + echo -e '\nERROR: The stub solution must not pass the tests!\n' + exit 1 + else + echo -e '\nSUCCESS: The stub solution failed the tests above as expected.\n' + fi - # Remove the built executable - rm -f ${exercise_name} else echo "Running all tests" for exercise in $(ls $exercises_path) @@ -54,4 +79,12 @@ function run_test() { fi } -run_test $@ \ No newline at end of file +# Delete the temp directory +function cleanup { + rm -rf ${tmp_path} +} + +# Register the cleanup function to be called on the EXIT signal +trap cleanup EXIT + +run_test $@ diff --git a/bin/verify-exercises b/bin/verify-exercises index 8d31328..e6dc10e 100755 --- a/bin/verify-exercises +++ b/bin/verify-exercises @@ -2,7 +2,7 @@ # Synopsis: # Test the track's exercises. -# +# # At a minimum, this file must check if the example/exemplar solution of each # Practice/Concept Exercise passes the exercise's tests. # @@ -22,7 +22,7 @@ for concept_exercise_dir in ./exercises/concept/*/; do if [ -d $concept_exercise_dir ]; then echo "Checking $(basename "${concept_exercise_dir}") exercise..." - # TODO: run command to verify that the exemplar solution passes the tests + bin/run-test.sh "$(basename "${concept_exercise_dir}")" fi done @@ -30,6 +30,6 @@ done for practice_exercise_dir in ./exercises/practice/*/; do if [ -d $practice_exercise_dir ]; then echo "Checking $(basename "${practice_exercise_dir}") exercise..." - # TODO: run command to verify that the example solution passes the tests + bin/run-test.sh "$(basename "${practice_exercise_dir}")" fi done diff --git a/config.json b/config.json index cb6448d..5303fca 100644 --- a/config.json +++ b/config.json @@ -26,27 +26,49 @@ ] }, "exercises": { - "concept": [], "practice": [ { - "uuid": "2cfe5afe-e94f-459c-aae5-d23d89931dda", "slug": "hello-world", "name": "Hello World", + "uuid": "2cfe5afe-e94f-459c-aae5-d23d89931dda", "practices": [], "prerequisites": [], "difficulty": 1 }, { - "uuid": "cd710981-b7de-4a80-beda-f6b95420a4d6", "slug": "leap", "name": "Leap", + "uuid": "cd710981-b7de-4a80-beda-f6b95420a4d6", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, + { + "slug": "difference-of-squares", + "name": "Difference Of Squares", + "uuid": "ce45a52e-0541-4384-8abf-b787bd49cbf7", + "practices": [], + "prerequisites": [], + "difficulty": 1 + }, + { + "slug": "grains", + "name": "Grains", + "uuid": "b5b9be18-9141-4176-8f8c-3dd14d14bed5", + "practices": [], + "prerequisites": [], + "difficulty": 2 + }, + { + "slug": "resistor-color", + "name": "Resistor Color", + "uuid": "ea5eb0a7-4a4a-4f08-b2e4-87a52bd64ce0", "practices": [], "prerequisites": [], "difficulty": 1 } ] }, - "concepts": [], "key_features": [ { "title": "Simple", @@ -67,21 +89,31 @@ "title": "Cross-compile", "content": "Odin can build for a plethora of targets and cross-compiling is a first class use case.", "icon": "cross-platform" + }, + { + "title": "Modern", + "content": "Odin is designed from the bottom up for the modern computer.", + "icon": "powerful" + }, + { + "title": "Fun", + "content": "Odin is the C alternative for the Joy of Programming.", + "icon": "fun" } ], "tags": [ + "execution_mode/compiled", "paradigm/imperative", "paradigm/procedural", - "typing/static", - "typing/strong", - "execution_mode/compiled", - "platform/windows", - "platform/mac", - "platform/linux", - "platform/ios", "platform/android", + "platform/ios", + "platform/linux", + "platform/mac", "platform/web", + "platform/windows", "runtime/standalone_executable", + "typing/static", + "typing/strong", "used_for/backends", "used_for/cross_platform_development", "used_for/embedded_systems", diff --git a/exercises/practice/difference-of-squares/.docs/instructions.md b/exercises/practice/difference-of-squares/.docs/instructions.md new file mode 100644 index 0000000..39c38b5 --- /dev/null +++ b/exercises/practice/difference-of-squares/.docs/instructions.md @@ -0,0 +1,14 @@ +# Instructions + +Find the difference between the square of the sum and the sum of the squares of the first N natural numbers. + +The square of the sum of the first ten natural numbers is +(1 + 2 + ... + 10)² = 55² = 3025. + +The sum of the squares of the first ten natural numbers is +1² + 2² + ... + 10² = 385. + +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. + +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json new file mode 100644 index 0000000..0ab7e56 --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "gvrooyen" + ], + "files": { + "solution": [ + "difference_of_squares.odin" + ], + "test": [ + "difference_of_squares_test.odin" + ], + "example": [ + ".meta/difference_of_squares_example.odin" + ] + }, + "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", + "source": "Problem 6 at Project Euler", + "source_url": "https://projecteuler.net/problem=6" +} diff --git a/exercises/practice/difference-of-squares/.meta/difference_of_squares_example.odin b/exercises/practice/difference-of-squares/.meta/difference_of_squares_example.odin new file mode 100644 index 0000000..e957639 --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/difference_of_squares_example.odin @@ -0,0 +1,14 @@ +package difference_of_squares + +square_of_sum :: proc(n: int) -> int { + sum := n * (n + 1) / 2 + return sum * sum +} + +sum_of_squares :: proc(n: int) -> int { + return n * (n + 1) * (2 * n + 1) / 6 +} + +difference :: proc(n: int) -> int { + return square_of_sum(n) - sum_of_squares(n) +} diff --git a/exercises/practice/difference-of-squares/.meta/tests.toml b/exercises/practice/difference-of-squares/.meta/tests.toml new file mode 100644 index 0000000..e54414c --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/tests.toml @@ -0,0 +1,37 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e46c542b-31fc-4506-bcae-6b62b3268537] +description = "Square the sum of the numbers up to the given number -> square of sum 1" + +[9b3f96cb-638d-41ee-99b7-b4f9c0622948] +description = "Square the sum of the numbers up to the given number -> square of sum 5" + +[54ba043f-3c35-4d43-86ff-3a41625d5e86] +description = "Square the sum of the numbers up to the given number -> square of sum 100" + +[01d84507-b03e-4238-9395-dd61d03074b5] +description = "Sum the squares of the numbers up to the given number -> sum of squares 1" + +[c93900cd-8cc2-4ca4-917b-dd3027023499] +description = "Sum the squares of the numbers up to the given number -> sum of squares 5" + +[94807386-73e4-4d9e-8dec-69eb135b19e4] +description = "Sum the squares of the numbers up to the given number -> sum of squares 100" + +[44f72ae6-31a7-437f-858d-2c0837adabb6] +description = "Subtract sum of squares from square of sums -> difference of squares 1" + +[005cb2bf-a0c8-46f3-ae25-924029f8b00b] +description = "Subtract sum of squares from square of sums -> difference of squares 5" + +[b1bf19de-9a16-41c0-a62b-1f02ecc0b036] +description = "Subtract sum of squares from square of sums -> difference of squares 100" diff --git a/exercises/practice/difference-of-squares/difference_of_squares.odin b/exercises/practice/difference-of-squares/difference_of_squares.odin new file mode 100644 index 0000000..799b420 --- /dev/null +++ b/exercises/practice/difference-of-squares/difference_of_squares.odin @@ -0,0 +1,13 @@ +package difference_of_squares + +square_of_sum :: proc(n: int) -> int { + #panic("Please implement the `square_of_sum` procedure") +} + +sum_of_squares :: proc(n: int) -> int { + #panic("Please implement the `sum_of_squares` procedure") +} + +difference :: proc(n: int) -> int { + #panic("Please implement the `difference` procedure") +} diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.odin b/exercises/practice/difference-of-squares/difference_of_squares_test.odin new file mode 100644 index 0000000..0ffc78c --- /dev/null +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.odin @@ -0,0 +1,53 @@ +/* These are the unit tests for the exercise. Only the first one is enabled to start with. You can + * enable the other tests by uncommenting the `@(test)` attribute of the test procedure. Your + * solution should pass all tests before it is ready for submission. + */ + +package difference_of_squares + +import "core:testing" + +@(test) +test_square_of_sum_1 :: proc(t: ^testing.T) { + testing.expect_value(t, square_of_sum(1), 1) +} + +// @(test) +test_square_of_sum_5 :: proc(t: ^testing.T) { + testing.expect_value(t, square_of_sum(5), 225) +} + +// @(test) +test_square_of_sum_100 :: proc(t: ^testing.T) { + testing.expect_value(t, square_of_sum(100), 25_502_500) +} + +// @(test) +sum_of_squares_1_test :: proc(t: ^testing.T) { + testing.expect_value(t, sum_of_squares(1), 1) +} + +// @(test) +sum_of_squares_5_test :: proc(t: ^testing.T) { + testing.expect_value(t, sum_of_squares(5), 55) +} + +// @(test) +sum_of_squares_100_test :: proc(t: ^testing.T) { + testing.expect_value(t, sum_of_squares(100), 338_350) +} + +// @(test) +difference_of_squares_1_test :: proc(t: ^testing.T) { + testing.expect_value(t, difference(1), 0) +} + +// @(test) +difference_of_squares_5_test :: proc(t: ^testing.T) { + testing.expect_value(t, difference(5), 170) +} + +// @(test) +difference_of_squares_100_test :: proc(t: ^testing.T) { + testing.expect_value(t, difference(100), 25_164_150) +} diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md new file mode 100644 index 0000000..df479fc --- /dev/null +++ b/exercises/practice/grains/.docs/instructions.md @@ -0,0 +1,15 @@ +# Instructions + +Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chess board, with the number of grains doubling on each successive square. + +There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). + +Write code that shows: + +- how many grains were on a given square, and +- the total number of grains on the chessboard diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json new file mode 100644 index 0000000..79969fe --- /dev/null +++ b/exercises/practice/grains/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "gvrooyen" + ], + "files": { + "solution": [ + "grains.odin" + ], + "test": [ + "grains_test.odin" + ], + "example": [ + ".meta/grains_example.odin" + ] + }, + "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://coderanch.com/wiki/718824/Grains" +} diff --git a/exercises/practice/grains/.meta/grains_example.odin b/exercises/practice/grains/.meta/grains_example.odin new file mode 100644 index 0000000..be44b1c --- /dev/null +++ b/exercises/practice/grains/.meta/grains_example.odin @@ -0,0 +1,35 @@ +package grains + +Error :: enum { + None = 0, + InvalidSquare, + NotImplemented, +} + +/* Calculate the number of grains on the specified square and return the resulting count, as well + as the sum of grains on this and all previous squares. +*/ +count :: proc(n: int) -> (u64, u64) { + acc: u64 = 1 + val: u64 = 1 + + for i := 2; i <= n; i += 1 { + val *= 2 + acc += val + } + + return val, acc +} + +// Returns the number of grains on the specified square. +square :: proc(n: int) -> (u64, Error) { + if n < 1 || n > 64 do return 0, .InvalidSquare + c, _ := count(n) + return c, .None +} + +// Returns the total number of squares on the board. +total :: proc() -> (u64, Error) { + _, t := count(64) + return t, .None +} diff --git a/exercises/practice/grains/.meta/tests.toml b/exercises/practice/grains/.meta/tests.toml new file mode 100644 index 0000000..6ea68bc --- /dev/null +++ b/exercises/practice/grains/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9fbde8de-36b2-49de-baf2-cd42d6f28405] +description = "returns the number of grains on the square -> grains on square 1" + +[ee1f30c2-01d8-4298-b25d-c677331b5e6d] +description = "returns the number of grains on the square -> grains on square 2" + +[10f45584-2fc3-4875-8ec6-666065d1163b] +description = "returns the number of grains on the square -> grains on square 3" + +[a7cbe01b-36f4-4601-b053-c5f6ae055170] +description = "returns the number of grains on the square -> grains on square 4" + +[c50acc89-8535-44e4-918f-b848ad2817d4] +description = "returns the number of grains on the square -> grains on square 16" + +[acd81b46-c2ad-4951-b848-80d15ed5a04f] +description = "returns the number of grains on the square -> grains on square 32" + +[c73b470a-5efb-4d53-9ac6-c5f6487f227b] +description = "returns the number of grains on the square -> grains on square 64" + +[1d47d832-3e85-4974-9466-5bd35af484e3] +description = "returns the number of grains on the square -> square 0 is invalid" + +[61974483-eeb2-465e-be54-ca5dde366453] +description = "returns the number of grains on the square -> negative square is invalid" + +[a95e4374-f32c-45a7-a10d-ffec475c012f] +description = "returns the number of grains on the square -> square greater than 64 is invalid" + +[6eb07385-3659-4b45-a6be-9dc474222750] +description = "returns the total number of grains on the board" diff --git a/exercises/practice/grains/grains.odin b/exercises/practice/grains/grains.odin new file mode 100644 index 0000000..9309232 --- /dev/null +++ b/exercises/practice/grains/grains.odin @@ -0,0 +1,14 @@ +package grains + +Error :: enum {}// Please inspect the tests to see which error states to enumerate here. + + +// Returns the number of grains on the specified square. +square :: proc(n: int) -> (u64, Error) { + #panic("Please implement the `square` procedure.") +} + +// Returns the total number of squares on the board. +total :: proc() -> (u64, Error) { + #panic("Please implement the `total` procedure.") +} diff --git a/exercises/practice/grains/grains_test.odin b/exercises/practice/grains/grains_test.odin new file mode 100644 index 0000000..710639f --- /dev/null +++ b/exercises/practice/grains/grains_test.odin @@ -0,0 +1,105 @@ +/* These are the unit tests for the exercise. Only the first one is enabled to start with. You can + * enable the other tests by uncommenting the `@(test)` attribute of the test procedure. Your + * solution should pass all tests before it is ready for submission. + */ + +package grains + +import "core:testing" + +@(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_1 :: proc( + t: ^testing.T, +) { + c, e := square(1) + testing.expect_value(t, c, 1) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_2 :: proc( + t: ^testing.T, +) { + c, e := square(2) + testing.expect_value(t, c, 2) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_3 :: proc( + t: ^testing.T, +) { + c, e := square(3) + testing.expect_value(t, c, 4) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_4 :: proc( + t: ^testing.T, +) { + c, e := square(4) + testing.expect_value(t, c, 8) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_16 :: proc( + t: ^testing.T, +) { + c, e := square(16) + testing.expect_value(t, c, 32_768) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_32 :: proc( + t: ^testing.T, +) { + c, e := square(32) + testing.expect_value(t, c, 2_147_483_648) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_grains_on_square_64 :: proc( + t: ^testing.T, +) { + c, e := square(64) + testing.expect_value(t, c, 9_223_372_036_854_775_808) + testing.expect_value(t, e, Error.None) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_square_0_raises_an_exception :: proc( + t: ^testing.T, +) { + c, e := square(0) + testing.expect_value(t, c, 0) + testing.expect_value(t, e, Error.InvalidSquare) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_negative_square_raises_an_exception :: proc( + t: ^testing.T, +) { + c, e := square(-1) + testing.expect_value(t, c, 0) + testing.expect_value(t, e, Error.InvalidSquare) +} + +// @(test) +test_returns_the_number_of_grains_on_the_square_square_greater_than_64_raises_an_exception :: proc( + t: ^testing.T, +) { + c, e := square(65) + testing.expect_value(t, c, 0) + testing.expect_value(t, e, Error.InvalidSquare) +} + +// @(test) +test_returns_the_total_number_of_grains_on_the_board :: proc(t: ^testing.T) { + c, e := total() + testing.expect_value(t, c, 18_446_744_073_709_551_615) + testing.expect_value(t, e, Error.None) +} diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md new file mode 100644 index 0000000..c9570e4 --- /dev/null +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -0,0 +1,16 @@ +# 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 diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json new file mode 100644 index 0000000..07f0497 --- /dev/null +++ b/exercises/practice/hello-world/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "gvrooyen" + ], + "files": { + "solution": [ + "hello_world.odin" + ], + "test": [ + "hello_world_test.odin" + ], + "example": [ + ".meta/hello_world_example.odin" + ] + }, + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", + "source": "This is an exercise to introduce users to using Exercism", + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" +} diff --git a/exercises/practice/hello-world/.meta/hello_world_example.odin b/exercises/practice/hello-world/.meta/hello_world_example.odin new file mode 100644 index 0000000..72190c1 --- /dev/null +++ b/exercises/practice/hello-world/.meta/hello_world_example.odin @@ -0,0 +1,5 @@ +package hello_world + +hello_world :: proc() -> string { + return "Hello, World!" +} diff --git a/exercises/practice/hello-world/.meta/tests.toml b/exercises/practice/hello-world/.meta/tests.toml new file mode 100644 index 0000000..73466d6 --- /dev/null +++ b/exercises/practice/hello-world/.meta/tests.toml @@ -0,0 +1,13 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[af9ffe10-dc13-42d8-a742-e7bdafac449d] +description = "Say Hi!" diff --git a/exercises/practice/hello-world/hello_world.odin b/exercises/practice/hello-world/hello_world.odin new file mode 100644 index 0000000..ab9d461 --- /dev/null +++ b/exercises/practice/hello-world/hello_world.odin @@ -0,0 +1,5 @@ +package hello_world + +hello_world :: proc() -> string { + return "Goodbye, Mars!" +} diff --git a/exercises/practice/hello-world/hello_world_test.odin b/exercises/practice/hello-world/hello_world_test.odin new file mode 100644 index 0000000..3793e9b --- /dev/null +++ b/exercises/practice/hello-world/hello_world_test.odin @@ -0,0 +1,10 @@ +package hello_world + +import "core:testing" + +@(test) +say_hi :: proc(t: ^testing.T) { + expected := "Hello, World!" + + testing.expect_value(t, hello_world(), expected) +} diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index dcbf411..6d5c31f 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -1,9 +1,17 @@ { - "authors": [], + "authors": [ + "gvrooyen" + ], "files": { - "solution": [], - "test": [], - "example": [] + "solution": [ + "leap.odin" + ], + "test": [ + "leap_test.odin" + ], + "example": [ + ".meta/leap_example.odin" + ] }, "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", diff --git a/exercises/practice/leap/.meta/leap_example.odin b/exercises/practice/leap/.meta/leap_example.odin new file mode 100644 index 0000000..f8501d0 --- /dev/null +++ b/exercises/practice/leap/.meta/leap_example.odin @@ -0,0 +1,5 @@ +package leap + +is_leap_year :: proc(year: int) -> bool { + return year % 400 == 0 || year % 4 == 0 && year % 100 != 0 +} diff --git a/exercises/practice/leap/.meta/tests.toml b/exercises/practice/leap/.meta/tests.toml new file mode 100644 index 0000000..ce6ba32 --- /dev/null +++ b/exercises/practice/leap/.meta/tests.toml @@ -0,0 +1,37 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[6466b30d-519c-438e-935d-388224ab5223] +description = "year not divisible by 4 in common year" + +[ac227e82-ee82-4a09-9eb6-4f84331ffdb0] +description = "year divisible by 2, not divisible by 4 in common year" + +[4fe9b84c-8e65-489e-970b-856d60b8b78e] +description = "year divisible by 4, not divisible by 100 in leap year" + +[7fc6aed7-e63c-48f5-ae05-5fe182f60a5d] +description = "year divisible by 4 and 5 is still a leap year" + +[78a7848f-9667-4192-ae53-87b30c9a02dd] +description = "year divisible by 100, not divisible by 400 in common year" + +[9d70f938-537c-40a6-ba19-f50739ce8bac] +description = "year divisible by 100 but not by 3 is still not a leap year" + +[42ee56ad-d3e6-48f1-8e3f-c84078d916fc] +description = "year divisible by 400 is leap year" + +[57902c77-6fe9-40de-8302-587b5c27121e] +description = "year divisible by 400 but not by 125 is still a leap year" + +[c30331f6-f9f6-4881-ad38-8ca8c12520c1] +description = "year divisible by 200, not divisible by 400 in common year" diff --git a/exercises/practice/leap/leap.odin b/exercises/practice/leap/leap.odin new file mode 100644 index 0000000..7056be7 --- /dev/null +++ b/exercises/practice/leap/leap.odin @@ -0,0 +1,5 @@ +package leap + +is_leap_year :: proc(year: int) -> bool { + #panic("Please implement the `is_leap_year` procedure.") +} diff --git a/exercises/practice/leap/leap_test.odin b/exercises/practice/leap/leap_test.odin new file mode 100644 index 0000000..5f08b87 --- /dev/null +++ b/exercises/practice/leap/leap_test.odin @@ -0,0 +1,61 @@ +/* These are the unit tests for the exercise. Only the first one is enabled to start with. You can + * enable the other tests by uncommenting the `@(test)` attribute of the test procedure. Your + * solution should pass all tests before it is ready for submission. + */ + +package leap + +import "core:testing" + +@(test) +test_not_divisible_by_4_in_common_year :: proc(t: ^testing.T) { + testing.expect(t, !is_leap_year(2015)) +} + +// @(test) +test_divisible_by_2_not_divisible_by_4_in_common_year :: proc(t: ^testing.T) { + testing.expect(t, !is_leap_year(1970)) +} + +// @(test) +test_divisible_by_4_not_divisible_by_100_in_leap_year :: proc(t: ^testing.T) { + testing.expect(t, is_leap_year(1996)) +} + +// @(test) +test_divisible_by_4_and_5_is_still_a_leap_year :: proc(t: ^testing.T) { + testing.expect(t, is_leap_year(1960)) +} + +// @(test) +test_divisible_by_100_not_divisible_by_400_in_common_year :: proc( + t: ^testing.T, +) { + testing.expect(t, !is_leap_year(2100)) +} + +// @(test) +test_divisible_by_100_but_not_by_3_is_still_not_a_leap_year :: proc( + t: ^testing.T, +) { + testing.expect(t, !is_leap_year(1900)) +} + +// @(test) +test_divisible_by_400 :: proc(t: ^testing.T) { + testing.expect(t, is_leap_year(2000)) +} + +// @(test) +test_divisible_by_400_but_not_by_125_is_still_a_leap_year :: proc( + t: ^testing.T, +) { + testing.expect(t, is_leap_year(2400)) +} + +// @(test) +test_divisible_by_200_not_divisible_by_400_in_common_year :: proc( + t: ^testing.T, +) { + testing.expect(t, !is_leap_year(1800)) +} diff --git a/exercises/practice/resistor-color/.docs/instructions.md b/exercises/practice/resistor-color/.docs/instructions.md new file mode 100644 index 0000000..0125e71 --- /dev/null +++ b/exercises/practice/resistor-color/.docs/instructions.md @@ -0,0 +1,39 @@ +# Instructions + +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. +For this exercise, you need to know two things about them: + +- Each resistor has a resistance value. +- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. + +To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. +Each band has a position and a numeric value. + +The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number. + +In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands. + +These colors are encoded as follows: + +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 + +The goal of this exercise is to create a way: + +- to look up the numerical value associated with a particular color band +- to list the different band colors + +Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: +Better Be Right Or Your Great Big Values Go Wrong. + +More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code]. + +[e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code diff --git a/exercises/practice/resistor-color/.meta/config.json b/exercises/practice/resistor-color/.meta/config.json new file mode 100644 index 0000000..b07cbca --- /dev/null +++ b/exercises/practice/resistor-color/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "gvrooyen" + ], + "files": { + "solution": [ + "resistor_color.odin" + ], + "test": [ + "resistor_color_test.odin" + ], + "example": [ + ".meta/resistor_color_example.odin" + ] + }, + "blurb": "Convert a resistor band's color to its numeric representation.", + "source": "Maud de Vries, Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/issues/1458" +} diff --git a/exercises/practice/resistor-color/.meta/resistor_color_example.odin b/exercises/practice/resistor-color/.meta/resistor_color_example.odin new file mode 100644 index 0000000..3b26336 --- /dev/null +++ b/exercises/practice/resistor-color/.meta/resistor_color_example.odin @@ -0,0 +1,33 @@ +package resistor_color + +Color :: enum { + Black, + Brown, + Red, + Orange, + Yellow, + Green, + Blue, + Violet, + Grey, + White, +} + +code :: proc(color: Color) -> int { + return int(color) +} + +colors :: proc() -> [10]Color { + return [10]Color { + .Black, + .Brown, + .Red, + .Orange, + .Yellow, + .Green, + .Blue, + .Violet, + .Grey, + .White, + } +} diff --git a/exercises/practice/resistor-color/.meta/tests.toml b/exercises/practice/resistor-color/.meta/tests.toml new file mode 100644 index 0000000..9d4ee97 --- /dev/null +++ b/exercises/practice/resistor-color/.meta/tests.toml @@ -0,0 +1,22 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[49eb31c5-10a8-4180-9f7f-fea632ab87ef] +description = "Color codes -> Black" + +[0a4df94b-92da-4579-a907-65040ce0b3fc] +description = "Color codes -> White" + +[5f81608d-f36f-4190-8084-f45116b6f380] +description = "Color codes -> Orange" + +[581d68fa-f968-4be2-9f9d-880f2fb73cf7] +description = "Colors" diff --git a/exercises/practice/resistor-color/resistor_color.odin b/exercises/practice/resistor-color/resistor_color.odin new file mode 100644 index 0000000..70b2607 --- /dev/null +++ b/exercises/practice/resistor-color/resistor_color.odin @@ -0,0 +1,12 @@ +package resistor_color + +Color :: enum {}// Implement an enumeration of all the resistor colors. + + +code :: proc(color: Color) -> int { + #panic("Please implement the `code` procedure.") +} + +colors :: proc() { // -> ? + #panic("Please implement the `colors` procedure.") +} diff --git a/exercises/practice/resistor-color/resistor_color_test.odin b/exercises/practice/resistor-color/resistor_color_test.odin new file mode 100644 index 0000000..e988003 --- /dev/null +++ b/exercises/practice/resistor-color/resistor_color_test.odin @@ -0,0 +1,79 @@ +/* These are the unit tests for the exercise. Only the tests for the `code()` + * procedure are enabled to start with. You can enable the final test by + * uncommenting the`@(test)` attribute of the `all_colors` test procedure. Your + * solution should pass all tests before it is ready for submission. + */ + +package resistor_color + +import "core:testing" + +@(test) +black :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Black), 0) +} + +@(test) +brown :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Brown), 1) +} + +@(test) +red :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Red), 2) +} + +@(test) +orange :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Orange), 3) +} + +@(test) +yellow :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Yellow), 4) +} + +@(test) +green :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Green), 5) +} + +@(test) +blue :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Blue), 6) +} + +@(test) +violet :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Violet), 7) +} + +@(test) +grey :: proc(t: ^testing.T) { + testing.expect_value(t, code(.Grey), 8) +} + +@(test) +white :: proc(t: ^testing.T) { + testing.expect_value(t, code(.White), 9) +} + +// @(test) +all_colors :: proc(t: ^testing.T) { + testing.expect_value( + t, + colors(), + [10]Color { + .Black, + .Brown, + .Red, + .Orange, + .Yellow, + .Green, + .Blue, + .Violet, + .Grey, + .White, + }, + ) +} diff --git a/ols.json b/ols.json index bf28ead..8a9df08 100644 --- a/ols.json +++ b/ols.json @@ -6,5 +6,6 @@ "enable_semantic_tokens": false, "enable_document_symbols": true, "enable_hover": true, + "enable_inlay_hints": true, "enable_snippets": true -} \ No newline at end of file +} diff --git a/src/odin_code_generator.odin b/src/odin_code_generator.odin index 3b515bf..66cf109 100644 --- a/src/odin_code_generator.odin +++ b/src/odin_code_generator.odin @@ -4,63 +4,51 @@ import "core:fmt" import "core:strings" Argument :: struct { - name: string, - type: string, + name: string, + type: string, value: any, } Function :: struct { - name: string, - args: []Argument, - ret: Argument + name: string, + args: []Argument, + ret: Argument, } Test :: struct { - name: string, - function: Function, - expected: any, - actual: any + name: string, + function: Function, + expected: any, + actual: any, } main :: proc() { - args := []Argument{ - Argument { - name = "abc", - type = "int", - value = 10 - }, - Argument { - name = "xyz", - type = "string", - value = "foo" - } - } - - function := Function { - name = "bob", - args = args, - ret = Argument { - name = "www", - type = "string", - value = "123" - } - } - - test := Test { - name = "should_do_the_thing", - function = function, - expected = "123" - } - - indent_level := 0 + args := []Argument { + Argument{name = "abc", type = "int", value = 10}, + Argument{name = "xyz", type = "string", value = "foo"}, + } + + function := Function { + name = "bob", + args = args, + ret = Argument{name = "www", type = "string", value = "123"}, + } + + test := Test { + name = "should_do_the_thing", + function = function, + expected = "123", + } + + indent_level := 0 b := strings.builder_make() - w(&b, indent_level, true, `// main.odin`) + w(&b, indent_level, true, `// main.odin`) generate_function(&b, indent_level, function) - w(&b, indent_level, true, ``) - w(&b, indent_level, true, `// test.odin`) + w(&b, indent_level, true, ``) + w(&b, indent_level, true, `// test.odin`) generate_test(&b, indent_level, test) - w(&b, indent_level, true, ``) + w(&b, indent_level, true, ``) fmt.printf("{}", strings.to_string(b)) } @@ -70,27 +58,56 @@ generate_function :: proc(b: ^strings.Builder, i: int, function: Function) { w(b, i, true, ``) w(b, i, false, `{} :: proc(`, function.name) - for arg, index in function.args { - generate_argument(b, 0, false, arg) + for arg, index in function.args { + generate_argument(b, 0, false, arg) - last_index := len(function.args) - 1 - if index != last_index { - w(b, 0, false, `, `) - } - } + last_index := len(function.args) - 1 + if index != last_index { + w(b, 0, false, `, `) + } + } w(b, i, true, `) -> {{`) - w(b, i+1, true, `{}: {} = {}`, function.ret.name, function.ret.type, function.ret.value) - w(b, i+1, true, `return {}`, function.ret.name) + w( + b, + i + 1, + true, + `{}: {} = {}`, + function.ret.name, + function.ret.type, + function.ret.value, + ) + w(b, i + 1, true, `return {}`, function.ret.name) w(b, i, true, `}}`) } -generate_argument :: proc(b: ^strings.Builder, i: int, newline: bool, argument: Argument) { - if argument.type == "string" { - w(b, i, newline, `{}: {} = "{}"`, argument.name, argument.type, argument.value) - } else { - w(b, i, newline, `{}: {} = {}`, argument.name, argument.type, argument.value) - } +generate_argument :: proc( + b: ^strings.Builder, + i: int, + newline: bool, + argument: Argument, +) { + if argument.type == "string" { + w( + b, + i, + newline, + `{}: {} = "{}"`, + argument.name, + argument.type, + argument.value, + ) + } else { + w( + b, + i, + newline, + `{}: {} = {}`, + argument.name, + argument.type, + argument.value, + ) + } } generate_test :: proc(b: ^strings.Builder, i: int, test: Test) { @@ -100,40 +117,46 @@ generate_test :: proc(b: ^strings.Builder, i: int, test: Test) { w(b, i, true, ``) w(b, i, true, `@(test)`) w(b, i, true, `test_{} :: proc(t: ^testing.T) {{`, test.name) - w(b, i+1, true, `expected := {}`, test.expected) + w(b, i + 1, true, `expected := {}`, test.expected) - for arg, index in test.function.args { - generate_argument(b, i+1, true, arg) - } + for arg, index in test.function.args { + generate_argument(b, i + 1, true, arg) + } - w(b, i+1, false, `result := {}(`, test.function.name) + w(b, i + 1, false, `result := {}(`, test.function.name) - for arg, index in test.function.args { - w(b, i, false, `{}`, arg.name) + for arg, index in test.function.args { + w(b, i, false, `{}`, arg.name) - last_index := len(test.function.args) - 1 - if index != last_index { - w(b, 0, false, `, `) - } - } + last_index := len(test.function.args) - 1 + if index != last_index { + w(b, 0, false, `, `) + } + } w(b, 0, true, `)`) - w(b, i+1, true, `testing.expect_value(t, result, expected)`) + w(b, i + 1, true, `testing.expect_value(t, result, expected)`) w(b, i, true, `)`) } // Write -w :: proc(b: ^strings.Builder, ind := 0, newline := true, format: string, args: ..any) { - indent(b, ind) - fmt.sbprintf(b, format, ..args) - - if newline { - fmt.sbprintf(b, "\n") - } +w :: proc( + b: ^strings.Builder, + ind := 0, + newline := true, + format: string, + args: ..any, +) { + indent(b, ind) + fmt.sbprintf(b, format, ..args) + + if newline { + fmt.sbprintf(b, "\n") + } } // Generates a number of tab/space characters indent :: proc(b: ^strings.Builder, ind: int) { - indent_rune := '\t' - for i in 0..