├── LICENSE.txt ├── libsum.sln ├── libsum ├── tests.c ├── libsum.h └── libsum.vcxproj └── README.md /LICENSE.txt: -------------------------------------------------------------------------------- 1 | LGPL: 2 | http://www.gnu.org/licenses/lgpl-2.1.html -------------------------------------------------------------------------------- /libsum.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsum", "libsum\libsum.vcxproj", "{C08A8D62-A531-4F49-BBA0-E781491CD75B}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {C08A8D62-A531-4F49-BBA0-E781491CD75B}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {C08A8D62-A531-4F49-BBA0-E781491CD75B}.Debug|Win32.Build.0 = Debug|Win32 14 | {C08A8D62-A531-4F49-BBA0-E781491CD75B}.Release|Win32.ActiveCfg = Release|Win32 15 | {C08A8D62-A531-4F49-BBA0-E781491CD75B}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /libsum/tests.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Unit tests for sums and pattern matching. 3 | * 4 | * License: LGPL 5 | * Copyright 2010 Sandro Magi 6 | */ 7 | 8 | #include 9 | #include 10 | #include "libsum.h" 11 | 12 | SUM(foo) { 13 | foo_one, 14 | foo_two, 15 | }; 16 | 17 | CASE(foo, foo_one) { int i; char c; }; 18 | CASE(foo, foo_two) { double d; }; 19 | 20 | int test(foo f) { 21 | MATCH(f) { 22 | AS(foo_one, y) 23 | printf("foo_one: %d, %c\n", y->i, y->c); 24 | return 1; 25 | AS(foo_two, y) 26 | printf("foo_two: %f\n", y->d); 27 | return 2; 28 | MATCHANY 29 | fprintf(stderr, "No such case %d!\n", *f); 30 | return 3; 31 | } 32 | } 33 | 34 | int main(int argc, char** argv) { 35 | { 36 | foo f; 37 | LET(f, foo_one, 3, 'g'); 38 | assert(1 == test(f)); 39 | free(f); 40 | } 41 | { 42 | foo x; 43 | LET(x, foo_two, 4.567); 44 | assert(2 == test(x)); 45 | 46 | /* test MATCHANY */ 47 | *x = (enum foo)4; 48 | assert(3 == test(x)); 49 | 50 | free(x); 51 | } 52 | /*printf("size foo_one: %d\n", sizeof(CTOR(foo_one))); 53 | printf("size foo_two: %d\n", sizeof(CTOR(foo_two)));*/ 54 | 55 | getc(stdin); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pattern Matching and Sum types for C 2 | This is a set of macros that provide the ability to declare and pattern match on sum types: 3 | 4 | /* declare a sum type and its constructor tags */ 5 | SUM(foo) { 6 | foo_one, 7 | foo_two, 8 | }; 9 | /* declare each sum case */ 10 | CASE(foo, foo_one) { int i; char c; }; 11 | CASE(foo, foo_two) { double d; }; 12 | 13 | void do_bar(foo f) { 14 | MATCH(f) { 15 | AS(foo_one, y) printf("foo_one: %d, %c\n", y->i, y->c); 16 | AS(foo_two, y) printf("foo_two: %d\n", y->d); 17 | MATCHANY 18 | fprintf(stderr, "No such case!"); 19 | exit(1); 20 | } 21 | } 22 | 23 | int main(int argc, char** argv) { 24 | foo f; 25 | LET(f, foo_one, (3, 'g')); /* (3,'g') is an initializer */ 26 | do_bar(f); 27 | } 28 | 29 | Note that each AS branch introduces a new scope, so you can declare new identifiers, unlike C's switch. 30 | 31 | # Notes 32 | 1. A MATCH statement must start in a position where new locals can be declared. In other words, in a new lexical scope, or after other locals have been declared. 33 | 2. MATCHANY is mandatory, and must always come last. 34 | 3. Each LET binding performs a dynamic memory allocation using malloc. The MAKE binding form allows you to specify a custom allocation function matching malloc's signature, but some form of allocation is needed. 35 | 36 | # Future Work 37 | Allow MATCH to discriminate on user-specified tag field names, eg. to allow matching on existing C unions instead of requiring them to adopt the SUM/CASE structure. 38 | 39 | # License 40 | My default license is LGPL v2, but I'm not sure how that interacts with the fact that the "library" consists solely of a header file, and thus has no runtime replaceable binary. I like the LGPL's conditions otherwise, so I'm open to other license suggestions that provide similar freedoms. 41 | -------------------------------------------------------------------------------- /libsum/libsum.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Disjoint unions, aka sum types, and pattern matching for C. 3 | * 4 | * License: LGPL 5 | * Copyright 2010 Sandro Magi 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* FIXME: make tag type user-definable, so language implementations 13 | * can define GC bits, struct size, etc. Possibly by factoring out 14 | * tag structure to a separate file? 15 | */ 16 | 17 | /* FIXME: consider sum extensions, such as adding a new case that never 18 | * existed before. Technically, any clients can define new cases by simply 19 | * calling providing new values not in the original enum. These will all 20 | * fail on MATCHANY, so perhaps we can provide an optional REDIRECT or DELEGATE 21 | * operation for "open recursion" ala dispatch. We would then have closed 22 | * and open sum types. This only works if new sum values don't conflict of 23 | * course. 24 | */ 25 | 26 | /* declare a sum type, which externally just looks like a pointer 27 | * to an enum */ 28 | #define SUM(name) typedef enum name* name; enum name 29 | 30 | /* declare a concrete instance of the sum, which declares its structure 31 | * and constructors */ 32 | #define CASE(sum, ctor) struct ctor 33 | 34 | #define CTOR(ctor) struct { unsigned tag; struct ctor data; } 35 | 36 | /* create a new instance of the given sum */ 37 | #define LET(var, ctor, ...) MAKE(var, ctor, malloc, __VA_ARGS__) 38 | 39 | /* provide a custom allocator */ 40 | #define MAKE(var, ctor, malloc, ...) do { \ 41 | struct ctor __tmp = { __VA_ARGS__ }; \ 42 | CTOR(ctor)* __tmpp = (CTOR(ctor)*)malloc(sizeof(CTOR(ctor))); \ 43 | __tmpp->tag = sizeof(struct ctor) << 8 | ctor; \ 44 | __tmpp->data = __tmp; \ 45 | var = &(__tmpp->tag); } while (0) 46 | 47 | /* deconstruct a sum type */ 48 | #define MATCH(X) int* MATCH_needs_new_scope = (int*)(X); switch(*MATCH_needs_new_scope & 0xFF) { 49 | 50 | /* match a particular constructor */ 51 | #define AS(ctor, var) break; } case ctor##: { struct ctor* var = FROM(ctor, MATCH_needs_new_scope); 52 | 53 | #define FROM(ctor, var) (struct ctor*)((char*)var + offsetof(CTOR(ctor), data)) 54 | 55 | /* match any constructor */ 56 | #define MATCHANY break; } default: 57 | -------------------------------------------------------------------------------- /libsum/libsum.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {C08A8D62-A531-4F49-BBA0-E781491CD75B} 15 | libsum 16 | 17 | 18 | 19 | Application 20 | true 21 | MultiByte 22 | 23 | 24 | Application 25 | false 26 | true 27 | MultiByte 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | EnableAllWarnings 43 | Disabled 44 | false 45 | false 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | Level3 54 | MaxSpeed 55 | true 56 | true 57 | 58 | 59 | true 60 | true 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | --------------------------------------------------------------------------------