From 5329634f583e4280233a2f479d69905080642626 Mon Sep 17 00:00:00 2001 From: jcmdln Date: Sat, 23 Sep 2023 11:46:09 -0400 Subject: [PATCH] Safe(r) math; Prep for evaluator --- .clang-tidy | 74 +++++++++++++++++++++++++------------- .github/workflows/lint.yml | 24 ++++++------- .gitignore | 3 ++ README.md | 4 +-- include/ploy/evaluator.h | 13 +++++++ include/ploy/math.h | 14 +++++--- include/ploy/printer.h | 13 +++++++ include/ploy/reader.h | 13 +++++++ meson.build | 14 ++++++-- ploy/demo.ploy | 12 +++++++ src/core.c | 10 +++--- src/evaluator.c | 15 ++++++++ src/main.c | 1 + src/math.c | 62 ++++++++++++++++++++++++-------- src/printer.c | 3 ++ src/reader.c | 13 ++++--- test/math/add.c | 17 +++++++-- test/math/divide.c | 8 +++-- test/math/multiply.c | 8 +++-- test/math/subtract.c | 19 +++++++--- 20 files changed, 257 insertions(+), 83 deletions(-) create mode 100644 include/ploy/evaluator.h create mode 100644 include/ploy/printer.h create mode 100644 include/ploy/reader.h create mode 100644 ploy/demo.ploy create mode 100644 src/evaluator.c diff --git a/.clang-tidy b/.clang-tidy index 8298992..f137043 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,28 +4,52 @@ --- FormatStyle: file HeaderFilterRegex: "" -Checks: > - -*, - bugprone-*, - -bugprone-easily-swappable-parameters, - cert-*-c, - clang-analyzer-*, - -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, - misc-*, - -misc-no-recursion, - performance-*, - portability-*, - readability-*, - -readability-braces-around-statements, - -readability-function-cognitive-complexity, - -readability-identifier-length, - -readability-magic-numbers, -WarningsAsErrors: > - -*, - bugprone-*, - cert-*-c, - clang-analyzer-*, - misc-*, - performance-*, - portability-*, - readability-*, +InheritParentConfig: false + +Checks: | + -* + bugprone-* + -bugprone-easily-swappable-parameters + cert-*-c + clang-analyzer-core + -clang-analyzer-core.uninitialized.NewArraySize + clang-analyzer-deadcode + clang-analyzer-nullability + clang-analyzer-security + -clang-analyzer-security.insecureAPI.decodeValueOfObjCType + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling + clang-analyzer-unix + clang-analyzer-valist + misc-* + -misc-no-recursion + performance-* + portability-* + readability-* + -readability-braces-around-statements + -readability-function-cognitive-complexity + -readability-identifier-length + -readability-magic-numbers + +WarningsAsErrors: | + -* + bugprone-* + -bugprone-easily-swappable-parameters + cert-*-c + clang-analyzer-core + -clang-analyzer-core.uninitialized.NewArraySize + clang-analyzer-deadcode + clang-analyzer-nullability + clang-analyzer-security + -clang-analyzer-security.insecureAPI.decodeValueOfObjCType + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling + clang-analyzer-unix + clang-analyzer-valist + misc-* + -misc-no-recursion + performance-* + portability-* + readability-* + -readability-braces-around-statements + -readability-function-cognitive-complexity + -readability-identifier-length + -readability-magic-numbers diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f4eb543..fc8f050 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,6 +9,7 @@ on: paths: - .clang-format - .clang-tidy + - .clangd - .github/workflows/lint.yml - .prettierrc.yml - include/**/*.h @@ -19,6 +20,7 @@ on: paths: - .clang-format - .clang-tidy + - .clangd - .github/workflows/lint.yml - .prettierrc.yml - include/**/*.h @@ -28,7 +30,7 @@ on: workflow_dispatch: jobs: - test: + lint: strategy: fail-fast: false matrix: @@ -37,7 +39,9 @@ jobs: - name: clang-tidy os: - name: ubuntu-latest - deps: libasan5 libgc-dev libreadline-dev libubsan1 meson pkgconf + deps: > + clang clang-format clang-tidy libasan5 libubsan1 lld meson + libgc-dev libreadline-dev name: ${{ matrix.linter.name }} (${{ matrix.os.name }}) runs-on: ${{ matrix.os.name }} @@ -55,23 +59,17 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y ${{ matrix.os.deps }} + sudo apt-get install -y --no-install-recommends --no-install-suggests ${{ matrix.os.deps }} - - name: Run Meson + - name: Test Ploy (Debug) env: CC: clang CC_LD: lld + NINJA: ninja run: | - meson --version - meson builddir -Dbuildtype=debugoptimized -Dwerror=true -Db_sanitize=address,undefined - - - name: Build Ploy (Debug) - run: | - ninja --version - ninja -C builddir + meson setup builddir -Db_sanitize=address,undefined -Dbuildtype=debugoptimized -Dwerror=true + ninja -C builddir test - name: Run ${{ matrix.linter.name }} run: | - ninja --version - ${{ matrix.linter.name }} --version ninja -C builddir ${{ matrix.linter.name }} diff --git a/.gitignore b/.gitignore index 63ef607..2caffed 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ !include/**/ !include/**/*.h !include/**/meson.build +!ploy/ +!ploy/**/ +!ploy/**/*.ploy !src/ !src/**/ !src/**/*.c diff --git a/README.md b/README.md index a692d4b..32ec985 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ By default, Meson will perform a simple release build. See `default_options` in ```sh sudo dnf install -y gc-devel meson pkgconf readline-devel -meson builddir +meson setup builddir ninja -C builddir ``` @@ -35,7 +35,7 @@ the specified options with Meson: ```sh sudo dnf install -y clang-tools-extra gc-devel libasan libubsan meson pkgconf readline-devel -meson builddir -Dbuildtype=debugoptimized -Dwerror=true -Db_sanitize=address,undefined +meson setup builddir -Db_sanitize=address,undefined -Dbuildtype=debugoptimized -Dwerror=true ninja -C builddir ``` diff --git a/include/ploy/evaluator.h b/include/ploy/evaluator.h new file mode 100644 index 0000000..959717b --- /dev/null +++ b/include/ploy/evaluator.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC +// +// Copyright (c) 2023 Johnathan C Maudlin + +#ifndef PLOY_EVALUATOR_H +#define PLOY_EVALUATOR_H +#pragma once + +#include "type.h" + +Object const *evaluator(Object const *object); + +#endif // PLOY_EVALUATOR_H diff --git a/include/ploy/math.h b/include/ploy/math.h index dfb180e..f92d4e8 100644 --- a/include/ploy/math.h +++ b/include/ploy/math.h @@ -8,12 +8,18 @@ #include "type.h" +// +// Arithmetic +// + Object const *Add(Object const *object); -Object const *Divide(Object const *object); -Object const *Multiply(Object const *object); Object const *Subtract(Object const *object); +Object const *Multiply(Object const *object); +Object const *Divide(Object const *object); -// Object const *Exponent(Object const *object); -// Object const *Modulo(Object const *object); +Object const *Exponent(Object const *object); +Object const *Log(Object const *object); +Object const *Modulo(Object const *object); +Object const *NthRoot(Object const *object); #endif // PLOY_MATH_H diff --git a/include/ploy/printer.h b/include/ploy/printer.h new file mode 100644 index 0000000..afbf691 --- /dev/null +++ b/include/ploy/printer.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC +// +// Copyright (c) 2023 Johnathan C Maudlin + +#ifndef PLOY_PRINTER_H +#define PLOY_PRINTER_H +#pragma once + +#include "type.h" + +void printer(Object const *object); + +#endif // PLOY_PRINTER_H diff --git a/include/ploy/reader.h b/include/ploy/reader.h new file mode 100644 index 0000000..71e988e --- /dev/null +++ b/include/ploy/reader.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: ISC +// +// Copyright (c) 2023 Johnathan C Maudlin + +#ifndef PLOY_READER_H +#define PLOY_READER_H +#pragma once + +#include "type.h" + +Object const *reader(char const *input); + +#endif // PLOY_READER_H diff --git a/meson.build b/meson.build index a8d4fdd..1432599 100644 --- a/meson.build +++ b/meson.build @@ -87,12 +87,17 @@ readline = dependency('readline', required: true) ploy_include_dirs = include_directories('include') ploy_include = files( 'include/ploy/core.h', + 'include/ploy/evaluator.h', + 'include/ploy/math.h', + 'include/ploy/printer.h', + 'include/ploy/reader.h', 'include/ploy/type.h', ) ploy_src_main = files('src/main.c') ploy_src = files( 'src/core.c', + 'src/evaluator.c', 'src/math.c', 'src/printer.c', 'src/reader.c', @@ -117,7 +122,10 @@ subdir('test') # Lints # -ploy_files = [] + ploy_include + ploy_src + ploy_src_main + ploy_test +run_target('clang-format', command: [ + 'clang-format', '--dry-run', '--verbose', '--Werror', + ploy_include, ploy_src, ploy_src_main, ploy_test]) -run_target('clang-format', command: ['clang-format', '--dry-run', '--Werror', ploy_files]) -run_target('clang-tidy', command: ['clang-tidy', '-p', '.', ploy_files]) +run_target('clang-tidy', command: [ + 'clang-tidy', '--quiet', '-p', 'builddir/', + ploy_include, ploy_src, ploy_src_main, ploy_test]) diff --git a/ploy/demo.ploy b/ploy/demo.ploy new file mode 100644 index 0000000..6b888fc --- /dev/null +++ b/ploy/demo.ploy @@ -0,0 +1,12 @@ +; This is a comment +# This is also a comment + +(define greeting "Hello, world!") + +(define greet (lambda (message) + (print message) +)) + +(greet greeting) + +(print (format "The answer is {}." (+ 40 2))) diff --git a/src/core.c b/src/core.c index ff2e68f..50310f6 100644 --- a/src/core.c +++ b/src/core.c @@ -8,15 +8,16 @@ #include #include - -void printer(Object const *object); -Object const *reader(char const *input); +#include +#include +#include Object const * Append(Object const *target, Object const *const object) { if (!object) return Error("Append: object is NULL"); if (!target || target->type == TYPE_NIL) return Cons(object, &NIL); + if (target->type != TYPE_LIST) target = Cons(target, &NIL); Object const *head = target; @@ -27,6 +28,7 @@ Append(Object const *target, Object const *const object) } if (cdr->type != TYPE_NIL) return Error("Append: cdr->type not of TYPE_NIL"); + head->list->cdr = Cons(object, &NIL); return target; } @@ -68,7 +70,7 @@ Eval(Object const *const object) { if (!object) return Error("Eval: object is NULL"); - return object; + return evaluator(object); } Object const * diff --git a/src/evaluator.c b/src/evaluator.c new file mode 100644 index 0000000..22022b2 --- /dev/null +++ b/src/evaluator.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: ISC +// +// Copyright (c) 2023 Johnathan C Maudlin + +#include + +#include + +#include + +Object const * +evaluator(Object const *object) +{ + return object; +} diff --git a/src/main.c b/src/main.c index 1c3f099..a1f8af1 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include #include +#include int repf(char const *path); int repl(void); diff --git a/src/math.c b/src/math.c index b17e293..48d1553 100644 --- a/src/math.c +++ b/src/math.c @@ -17,7 +17,13 @@ Add(Object const *object) while (object->type == TYPE_LIST) { Object const *car = Car(object); if (car->type != TYPE_NUMBER) return Error("Add: car not of TYPE_NUMBER"); - result->number += car->number; + + int64_t r = result->number; + int64_t c = car->number; + if ((c > 0 && r > (INT64_MAX - c)) || (c < 0 && r < (INT64_MIN - c))) + return Error("Add: overflow"); + + result->number += c; object = Cdr(object); } @@ -38,21 +44,31 @@ Divide(Object const *object) object = Cdr(object); while (object->type == TYPE_LIST) { car = Car(object); - result->number = (result->number / car->number); + result->number /= car->number; object = Cdr(object); } return result; } -// Object const * -// Exponent(Object const *object) -// { -// if (!object) return Error("Exponent: object is NULL"); -// if (object->type != TYPE_LIST) return Error("Exponent: object not of TYPE_LIST"); +Object const * +Exponent(Object const *object) +{ + if (!object) return Error("Exponent: object is NULL"); + if (object->type != TYPE_LIST) return Error("Exponent: object not of TYPE_LIST"); -// Object *result = Number(0); + Object *result = Number(0); + return result; +} + +// Log(Object const *object) +// { +// if (!object) return Error("Log: object is NULL"); +// if (object->type != TYPE_LIST) return Error("Log: object not of TYPE_LIST"); +// +// Object *result = Number(0); +// // return result; // } @@ -61,9 +77,9 @@ Divide(Object const *object) // { // if (!object) return Error("Modulo: object is NULL"); // if (object->type != TYPE_LIST) return Error("Modulo: object not of TYPE_LIST"); - +// // Object *result = Number(0); - +// // return result; // } @@ -81,26 +97,44 @@ Multiply(Object const *object) object = Cdr(object); while (object->type == TYPE_LIST) { car = Car(object); - result->number = (result->number * car->number); + result->number *= car->number; object = Cdr(object); } return result; } +// Object const * +// NthRoot(Object const *object) +// { +// if (!object) return Error("Root: object is NULL"); +// if (object->type != TYPE_LIST) return Error("Root: object not of TYPE_LIST"); +// +// Object *result = Number(0); +// +// return result; +// } + Object const * Subtract(Object const *object) { if (!object) return Error("Subtract: object is NULL"); if (object->type != TYPE_LIST) return Error("Subtract: object not of TYPE_LIST"); - Object *result = Number(0); + int64_t result = Car(object)->number; + object = Cdr(object); + while (object->type == TYPE_LIST) { Object const *car = Car(object); if (car->type != TYPE_NUMBER) return Error("Subtract: car not of TYPE_NUMBER"); - result->number -= car->number; + + int64_t c = car->number; + if ((c > 0 && result < (INT64_MIN + c)) || (c < 0 && result > (INT64_MAX + c))) + return Error("Subtract: overflow"); + + result -= c; object = Cdr(object); } - return result; + return Number(result); } diff --git a/src/printer.c b/src/printer.c index b0af1f3..7aa76e2 100644 --- a/src/printer.c +++ b/src/printer.c @@ -23,6 +23,9 @@ printer(Object const *object) case TYPE_ERROR: printf("error: %s\n", object->string); break; + // case TYPE_LAMBDA: + // printf("lambda"); + // break; case TYPE_LIST: print_list(object); break; diff --git a/src/reader.c b/src/reader.c index c946b49..324d70d 100644 --- a/src/reader.c +++ b/src/reader.c @@ -10,7 +10,6 @@ #include -// static Object const *SYMBOLS = &NIL; static char const TOKENS[11] = " \n\r\t\v;()\"\0"; Object const *read_delimiters(char const *input, char begin, char end); @@ -56,7 +55,7 @@ read_delimiters(char const *const input, char const begin, char const end) } Object const * -read_form(size_t const *index, char const **input) +read_form(size_t const *index, char const **const input) { if (!input) return Error("reader: input is NULL"); @@ -80,6 +79,7 @@ read_form(size_t const *index, char const **input) // Comment case ';': + case '#': while (*cursor && *++cursor != '\n') ++length; continue; @@ -95,11 +95,10 @@ read_form(size_t const *index, char const **input) // Number case '-': case '+': - if (isdigit(cursor[1])) { + if (isdigit(cursor[1])) object = read_number(&length, &cursor); - } else { + else object = read_symbol(&length, &cursor); - } break; case '0': case '1': @@ -182,7 +181,7 @@ read_number(size_t *const index, char const **const input) } Object const * -read_string(size_t *const index, char const **input) +read_string(size_t *const index, char const **const input) { char const *cursor = *input; size_t length = 0; @@ -209,7 +208,7 @@ read_string(size_t *const index, char const **input) } Object const * -read_symbol(size_t *const index, char const **input) +read_symbol(size_t *const index, char const **const input) { char const *cursor = *input; size_t length = 0; diff --git a/test/math/add.c b/test/math/add.c index 7d5106a..5f96487 100644 --- a/test/math/add.c +++ b/test/math/add.c @@ -19,12 +19,23 @@ main(void) assert(nil != NULL); assert(nil->type == TYPE_ERROR); - // (+ 1 2 3 4 32) - Object const *add = Add( - Cons(Number(1), Cons(Number(2), Cons(Number(3), Cons(Number(4), Cons(Number(32), &NIL)))))); + Object const *overflow = Add(Cons(Number(INT64_MAX), Cons(Number(1), &NIL))); + assert(overflow != NULL); + assert(overflow->type == TYPE_ERROR); + + Object const *underflow = Add(Cons(Number(INT64_MIN), Cons(Number(-1), &NIL))); + assert(underflow != NULL); + assert(underflow->type == TYPE_ERROR); + + Object const *add = Add(Read("30 10 2")); assert(add != NULL); assert(add->type == TYPE_NUMBER); assert(add->number == 42); + Object const *add_alt = Add(Cons(Number(30), Cons(Number(10), Cons(Number(2), &NIL)))); + assert(add_alt != NULL); + assert(add_alt->type == TYPE_NUMBER); + assert(add->number == add_alt->number); + return EXIT_SUCCESS; } diff --git a/test/math/divide.c b/test/math/divide.c index 91a7d26..e0451de 100644 --- a/test/math/divide.c +++ b/test/math/divide.c @@ -19,11 +19,15 @@ main(void) assert(nil != NULL); assert(nil->type == TYPE_ERROR); - // (/ 42 7 3) - Object const *divide = Divide(Cons(Number(42), Cons(Number(7), Cons(Number(3), &NIL)))); + Object const *divide = Divide(Read("42 7 3")); assert(divide != NULL); assert(divide->type == TYPE_NUMBER); assert(divide->number == 2); + Object const *divide_alt = Divide(Cons(Number(42), Cons(Number(7), Cons(Number(3), &NIL)))); + assert(divide_alt != NULL); + assert(divide_alt->type == TYPE_NUMBER); + assert(divide->number == divide_alt->number); + return EXIT_SUCCESS; } diff --git a/test/math/multiply.c b/test/math/multiply.c index 366bf0e..a1746a7 100644 --- a/test/math/multiply.c +++ b/test/math/multiply.c @@ -19,11 +19,15 @@ main(void) assert(nil != NULL); assert(nil->type == TYPE_ERROR); - // (* 2 3 7) - Object const *multiply = Multiply(Cons(Number(2), Cons(Number(3), Cons(Number(7), &NIL)))); + Object const *multiply = Multiply(Read("2 3 7")); assert(multiply != NULL); assert(multiply->type == TYPE_NUMBER); assert(multiply->number == 42); + Object const *multiply_alt = Multiply(Cons(Number(2), Cons(Number(3), Cons(Number(7), &NIL)))); + assert(multiply_alt != NULL); + assert(multiply_alt->type == TYPE_NUMBER); + assert(multiply->number == multiply_alt->number); + return EXIT_SUCCESS; } diff --git a/test/math/subtract.c b/test/math/subtract.c index 80ba5d4..3bde7c2 100644 --- a/test/math/subtract.c +++ b/test/math/subtract.c @@ -19,12 +19,23 @@ main(void) assert(nil != NULL); assert(nil->type == TYPE_ERROR); - // (- 1 2 3 4 32) - Object const *subtract = Subtract( - Cons(Number(1), Cons(Number(2), Cons(Number(3), Cons(Number(4), Cons(Number(32), &NIL)))))); + Object const *overflow = Subtract(Cons(Number(INT64_MAX), Cons(Number(-1), &NIL))); + assert(overflow != NULL); + assert(overflow->type == TYPE_ERROR); + + Object const *underflow = Subtract(Cons(Number(INT64_MIN), Cons(Number(1), &NIL))); + assert(underflow != NULL); + assert(underflow->type == TYPE_ERROR); + + Object const *subtract = Subtract(Read("100 58")); assert(subtract != NULL); assert(subtract->type == TYPE_NUMBER); - assert(subtract->number == -42); + assert(subtract->number == 42); + + Object const *subtract_alt = Subtract(Cons(Number(100), Cons(Number(58), &NIL))); + assert(subtract_alt != NULL); + assert(subtract_alt->type == TYPE_NUMBER); + assert(subtract->number == subtract_alt->number); return EXIT_SUCCESS; }