Compare commits

4 Commits

Author SHA1 Message Date
7c08e8da4d Fixed depends. 2025-09-12 16:33:45 -04:00
fdf526750d Cleaned up more fdef stuff. 2025-09-12 16:32:58 -04:00
b102a32999 Cleaned up fdef detritus. 2025-08-30 09:47:48 -04:00
518f2e9803 Changes to nomenclature.
"Kind" is how user-level types are called internally, to avoid
confusion. Kind literals in the AST have also been renamed to better
represent their function.
2025-08-29 09:56:40 -04:00
11 changed files with 89 additions and 156 deletions

View File

@@ -1,20 +1,20 @@
# SCL: Simple CAS Language
*v0.3*
Version v0.3
SCL aims to be a human-friendly Computer Algebra System (CAS) inspired by
[maxima](https://maxima.sourceforge.io/) that feels like writing on paper. In
its current state, SCL can be used as a functional programming language capable
of performing simple arithmetic. The codebase is about 2,000 lines of
handwritten C, including a parser, interpreter, and runtime. It uses a linked
environment scoping model.
of performing simple arithmetic. The codebase is about 2,000 lines of C,
including a parser, interpreter, and runtime. It uses a linked environment
scoping model.
## Usage
To download and run:
```bash
git clone https://git.signorovitch.org/scl/scl -b stable && cd scl
git clone https://git.signorovitch.org/jacob/scl -b stable && cd scl
make release
./scl.out
```
@@ -22,25 +22,35 @@ make release
### For Development
```bash
git clone git@signorovitch.org:scl/scl --recurse-submodules && cd scl
git clone git@signorovitch.org:jacob/scl --recurse-submodules && cd scl
make all test
./scl.out
```
If you wish to run tests, make sure to run `git clone --recurse-submodules` to
include the [Unity](https://github.com/ThrowTheSwitch/Unity) test framework.
*Note that tests are currently in poor use. I hope to amend this in the future.*
## Syntax
SCL's syntax will feel familiar to other functional programming languages.
As one would expect, you can evaluate simple infix expressions:
```scl
> x = 3 + 3 * 3; x + 1
= 13
> f(x) x + 1
> f(1)
> 1 + 1
= 2
```
You can also define your own functions and variables:
```scl
> f(x) 2 * x
> n = 3
> f(n)
= 6
```
Being a functional programming language at heart, one can of course use lambda functions:
```scl
> (\(x) 2 * x)(5)
= 10
> f(g) g(2)
@@ -48,7 +58,7 @@ SCL's syntax will feel familiar to other functional programming languages.
= 4
```
Here's a simple factorial function, using recursion:
Here's a simple factorial function:
```scl
> factorial(n) {
@@ -57,8 +67,7 @@ Here's a simple factorial function, using recursion:
> }
```
SCL's syntax is quite flexible. The above function could be more concisely
written as:
Or, using SCL's more concise syntax:
```scl
> factorial(n) ? n == 0 1 n * factorial(n - 1)

View File

@@ -19,13 +19,13 @@
- [ ] Parse lists/arrays/vectors
- [x] Parse blocks
- [ ] Parse control flow
- [ ] Parse `if` statements
- [x] Parse `if` statements
- [ ] Parse `loop`s
- [ ] Parse `for` loops
- [ ] Parse `while` loops
- [ ] Parse `case` statements
- [ ] Parse `goto` statements
- [ ] Parse lambda function definition
- [x] Parse lambda function definition
- [ ] Parse function calling with positional arguments
- [ ] Parse variadic functions
- [ ] Parse infix function definition
@@ -38,7 +38,7 @@
- [ ] Exec symbolic variables
- [ ] Exec control flow statements
- [ ] Exec variadic functions
- [ ] Exec lambda functions
- [x] Exec lambda functions
- [ ] Exec lists
- [ ] Exec arrays
- [ ] Exec vectors

View File

@@ -39,13 +39,12 @@ void ast_destroy(AST* ast) {
if (!ast) return;
switch (ast->type) {
case AST_TYPE_NUM: ast_num_data_destroy(ast->data); break;
case AST_TYPE_BOOL: ast_bool_data_destroy(ast->data); break;
case AST_TYPE_LIT_NUM: ast_num_data_destroy(ast->data); break;
case AST_TYPE_LIT_BOOL: ast_bool_data_destroy(ast->data); break;
case AST_TYPE_CALL: ast_call_data_destroy(ast->data); break;
case AST_TYPE_VREF: ast_vref_data_destroy(ast->data); break;
case AST_TYPE_VDEF: ast_vdef_data_destroy(ast->data); break;
case AST_TYPE_BLOCK: ast_block_data_destroy_psv(ast->data); break;
case AST_TYPE_FDEF: ast_fdef_data_destroy_psv(ast->data); break;
case AST_TYPE_ARG: ast_arg_data_destroy(ast->data); break;
case AST_TYPE_BIF: ast_bif_data_destroy(ast->data); break;
case AST_TYPE_EXC: ast_exc_data_destroy(ast->data); break;
@@ -182,30 +181,6 @@ void ast_block_data_destroy_psv(ASTBlockData* block) {
free(block);
}
ASTFDefData*
ast_fdef_data_init(char* name, size_t argc, AST** argv, AST* body) {
ASTFDefData* fdef = malloc(sizeof(ASTFDefData));
fdef->name = name;
fdef->argc = argc;
fdef->argv = argv;
fdef->body = body;
return fdef;
}
void ast_fdef_data_destroy(ASTFDefData* fdef) {
free(fdef->name);
for (int i = 0; i < fdef->argc; ast_destroy(fdef->argv[i++]));
ast_destroy(fdef->body);
}
void ast_fdef_data_destroy_psv(ASTFDefData* fdef) {
free(fdef->name);
free(fdef->argv);
free(fdef);
}
ASTArgData* ast_arg_data_init(char* name) {
ASTArgData* arg = malloc(sizeof(ASTArgData));
arg->name = name;

View File

@@ -7,13 +7,13 @@
static char* asttype_names[] = {
[AST_TYPE_CALL] = "FUNC CALL",
[AST_TYPE_NUM] = "NUMBER",
[AST_TYPE_BOOL] = "BOOLEAN",
[AST_TYPE_LIT_NUM] = "NUMBER",
[AST_TYPE_LIT_BOOL] = "BOOLEAN",
[AST_TYPE_LIT_KIND] = "KIND",
[AST_TYPE_VREF] = "VAR REFERENCE",
[AST_TYPE_VDEF] = "VAR DEFINITION",
[AST_TYPE_BLOCK] = "BLOCK",
[AST_TYPE_EXC] = "EXCEPTION",
[AST_TYPE_FDEF] = "FUNCTION DEFINITION",
[AST_TYPE_ARG] = "DEFINITION ARGUMENT",
[AST_TYPE_LAMBDA] = "LAMBDA EXPRESSION",
[AST_TYPE_BIF] = "BUILTIN FUNCTION"
@@ -31,10 +31,10 @@ void ast_print_i(AST* ast, int i) {
INDENT_FIELD("type", "%s", asttype_names[ast->type]);
INDENT_FIELD_EXT_NONL_START("data");
switch (ast->type) {
case AST_TYPE_NUM:
case AST_TYPE_LIT_NUM:
printf("%s %lf\n", INDENT_spacing->buf, *(ASTNumData*)ast->data);
break;
case AST_TYPE_BOOL:
case AST_TYPE_LIT_BOOL:
printf(
"%s %s\n", INDENT_spacing->buf,
*(ASTBoolData*)ast->data ? "true" : "false"
@@ -45,7 +45,6 @@ void ast_print_i(AST* ast, int i) {
case AST_TYPE_VREF: ast_vref_print(ast->data, i + 2); break;
case AST_TYPE_VDEF: ast_vdef_print(ast->data, i + 2); break;
case AST_TYPE_BLOCK: ast_block_print(ast->data, i + 2); break;
case AST_TYPE_FDEF: ast_fdef_print(ast->data, i + 2); break;
case AST_TYPE_ARG: ast_arg_print(ast->data, i + 2); break;
case AST_TYPE_LAMBDA: ast_lambda_print(ast->data, i + 2); break;
case AST_TYPE_BIF: ast_bif_print(ast->data, i + 2); break;
@@ -128,18 +127,6 @@ void ast_block_print(ASTBlockData* data, int depth) {
INDENT_END;
}
void ast_fdef_print(ASTFDefData* fdef, int i) {
INDENT_BEGIN(i)
INDENT_TITLE("ASTFDefData", fdef);
INDENT_FIELD("name", "%s", fdef->name);
INDENT_FIELD("argc", "%ld", fdef->argc);
INDENT_FIELD_LIST("argv", fdef->argv, fdef->argc, ast_print_i);
INDENT_FIELD_EXT_NONL_START("body");
ast_print_i(fdef->body, i + 2);
INDENT_FIELD_NONL_END;
INDENT_END;
}
void ast_arg_print(ASTArgData* arg, int i) {
INDENT_BEGIN(i);
INDENT_TITLE("ASTArgData", arg);

View File

@@ -15,7 +15,7 @@ AST* builtin_sum(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`sum` encountered an exception.", arg)
);
if (arg->type != AST_TYPE_NUM)
if (arg->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Sum can't sum some non-num arguments.", NULL)
@@ -24,11 +24,11 @@ AST* builtin_sum(size_t argc, AST** argv, Scope* parent) {
total += *(ASTNumData*)arg->data;
}
return ast_init(AST_TYPE_NUM, ast_num_data_init(total));
return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(total));
}
AST* builtin_sub(size_t argc, AST** argv, Scope* parent) {
if (argc <= 0) return ast_init(AST_TYPE_NUM, ast_num_data_init(0));
if (argc <= 0) return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(0));
AST* first = exec_exp(*argv, parent);
if (first->type == AST_TYPE_EXC)
@@ -36,7 +36,7 @@ AST* builtin_sub(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`sub` encountered an exception.", first)
);
if (first->type != AST_TYPE_NUM)
if (first->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't subtract non-num arguments.", NULL)
@@ -51,7 +51,7 @@ AST* builtin_sub(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`sub` encountered an exception.", arg)
);
if (arg->type != AST_TYPE_NUM)
if (arg->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't subtract non-num arguments.", NULL)
@@ -60,11 +60,11 @@ AST* builtin_sub(size_t argc, AST** argv, Scope* parent) {
total -= *(ASTNumData*)arg->data;
}
return ast_init(AST_TYPE_NUM, ast_num_data_init(total));
return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(total));
}
AST* builtin_mul(size_t argc, AST** argv, Scope* parent) {
if (argc <= 0) return ast_init(AST_TYPE_NUM, ast_num_data_init(0));
if (argc <= 0) return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(0));
AST* first = exec_exp(*argv, parent);
if (first->type == AST_TYPE_EXC)
@@ -72,7 +72,7 @@ AST* builtin_mul(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`mul` encountered an expection.", first)
);
if (first->type != AST_TYPE_NUM)
if (first->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't multiply non-num arguments.", NULL)
@@ -87,7 +87,7 @@ AST* builtin_mul(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`mul` encountered an execption.", arg)
);
if (arg->type != AST_TYPE_NUM)
if (arg->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't multiply non-num arguments.", NULL)
@@ -96,11 +96,11 @@ AST* builtin_mul(size_t argc, AST** argv, Scope* parent) {
total *= *(ASTNumData*)arg->data;
}
return ast_init(AST_TYPE_NUM, ast_num_data_init(total));
return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(total));
}
AST* builtin_div(size_t argc, AST** argv, Scope* parent) {
if (argc <= 0) return ast_init(AST_TYPE_NUM, ast_num_data_init(0));
if (argc <= 0) return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(0));
AST* first = exec_exp(*argv, parent);
if (first->type == AST_TYPE_EXC)
@@ -108,7 +108,7 @@ AST* builtin_div(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`div` encountered an exception.", first)
);
if (first->type != AST_TYPE_NUM)
if (first->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't divide non-num arguments.", NULL)
@@ -123,7 +123,7 @@ AST* builtin_div(size_t argc, AST** argv, Scope* parent) {
AST_TYPE_EXC,
ast_exc_data_init("`div` encountered an exception.", arg)
);
if (arg->type != AST_TYPE_NUM)
if (arg->type != AST_TYPE_LIT_NUM)
return ast_init(
AST_TYPE_EXC,
ast_exc_data_init("Can't divide non-num arguments.", NULL)
@@ -132,7 +132,7 @@ AST* builtin_div(size_t argc, AST** argv, Scope* parent) {
total /= *(ASTNumData*)arg->data;
}
return ast_init(AST_TYPE_NUM, ast_num_data_init(total));
return ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(total));
}
AST* builtin_die(size_t argc, AST** argv, Scope* parent) {
@@ -150,7 +150,7 @@ AST* builtin_if(size_t argc, AST** argv, Scope* parent) {
AST* body = argv[1];
AST* alt = argv[2];
if (pred->type != AST_TYPE_BOOL) {
if (pred->type != AST_TYPE_LIT_BOOL) {
if (pred->type == AST_TYPE_EXC) {
return ast_init(
AST_TYPE_EXC, ast_exc_data_init("if touched an error", pred)
@@ -169,7 +169,8 @@ AST* builtin_if(size_t argc, AST** argv, Scope* parent) {
AST* builtin_eq(size_t argc, AST** argv, Scope* parent) {
if (argc < 1) return ast_init(AST_TYPE_EXC, ast_exc_data_init("bad", NULL));
else if (argc == 1) return ast_init(AST_TYPE_BOOL, ast_bool_data_init(1));
else if (argc == 1)
return ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(1));
AST* first = exec_exp(argv[0], parent);
ASTType type = first->type;
@@ -194,17 +195,17 @@ AST* builtin_eq(size_t argc, AST** argv, Scope* parent) {
// delegated to each type. For now this works.
switch (type) {
case AST_TYPE_NUM:
case AST_TYPE_LIT_NUM:
if (*(ASTNumData*)first->data == *(ASTNumData*)second->data)
return ast_init(AST_TYPE_BOOL, ast_bool_data_init(1));
else return ast_init(AST_TYPE_BOOL, ast_bool_data_init(0));
case AST_TYPE_BOOL:
return ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(1));
else return ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(0));
case AST_TYPE_LIT_BOOL:
if (*(ASTNumData*)first->data == *(ASTNumData*)second->data)
return ast_init(AST_TYPE_BOOL, ast_bool_data_init(1));
else return ast_init(AST_TYPE_BOOL, ast_bool_data_init(0));
return ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(1));
else return ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(0));
default:
return ast_init(
AST_TYPE_BOOL, ast_bool_data_init(0)
AST_TYPE_LIT_BOOL, ast_bool_data_init(0)
); // Can't equate nonprimatives. I think. Maybe.
}
}

View File

@@ -34,17 +34,16 @@ AST* exec_exp(AST* ast, Scope* parent) {
switch (ast->type) {
case AST_TYPE_BLOCK: return exec_block(ast, parent);
case AST_TYPE_CALL: return exec_call(ast, parent);
case AST_TYPE_NUM:
case AST_TYPE_LIT_NUM:
return ast_init(
AST_TYPE_NUM, ast_num_data_init(*(ASTNumData*)ast->data)
AST_TYPE_LIT_NUM, ast_num_data_init(*(ASTNumData*)ast->data)
);
case AST_TYPE_BOOL:
case AST_TYPE_LIT_BOOL:
return ast_init(
AST_TYPE_BOOL, ast_bool_data_init(*(ASTBoolData*)ast->data)
AST_TYPE_LIT_BOOL, ast_bool_data_init(*(ASTBoolData*)ast->data)
);
case AST_TYPE_VREF: return exec_vref(ast, parent);
case AST_TYPE_VDEF: return exec_vdef(ast, parent);
case AST_TYPE_FDEF: return exec_fdef(ast, parent);
case AST_TYPE_BIF:
case AST_TYPE_LAMBDA: return ast;
default: printf("what\n"); exit(1);
@@ -71,8 +70,9 @@ AST* exec_call(AST* ast, Scope* parent) {
switch (exp->type) {
case AST_TYPE_BIF:
ASTBIFData bifdata = exp->data;
return bifdata(calldata->argc, calldata->argv, parent);
return ((ASTBIFData) exp->data)(
calldata->argc, calldata->argv, parent
);
case AST_TYPE_LAMBDA:
return exec_lambda(calldata->argc, calldata->argv, exp, parent);
default:
@@ -115,15 +115,6 @@ AST* exec_vref(AST* ast, Scope* parent) {
return found;
}
AST* exec_fdef(AST* ast, Scope* parent) {
ast->scope = scope_init(parent);
ASTFDefData* fdef = (ASTFDefData*)ast->data;
AST* val = ast;
char* key = fdef->name;
scope_add(parent, key, val);
return fdef->body; // Function definitions return function body.
}
AST* exec_lambda(size_t argc, AST** argv, AST* exp, Scope* parent) {
Scope* callscope = scope_init(parent);
ASTLambdaData* lambda = (ASTLambdaData*)exp->data;

View File

@@ -134,10 +134,10 @@ block:
exp:
// Number.
NUM { $$ = ast_init(AST_TYPE_NUM, ast_num_data_init($1)); }
NUM { $$ = ast_init(AST_TYPE_LIT_NUM, ast_num_data_init($1)); }
| BOOLT { $$ = ast_init(AST_TYPE_BOOL, ast_bool_data_init(1)); }
| BOOLF { $$ = ast_init(AST_TYPE_BOOL, ast_bool_data_init(0)); }
| BOOLT { $$ = ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(1)); }
| BOOLF { $$ = ast_init(AST_TYPE_LIT_BOOL, ast_bool_data_init(0)); }
| exp DEQ exp {
AST** argv = calloc(2, sizeof(AST*));
@@ -245,7 +245,7 @@ exp:
// Negative.
| SUB exp {
AST** argv = calloc(2, sizeof(AST*));
argv[0] = ast_init(AST_TYPE_NUM, ast_num_data_init(-1));
argv[0] = ast_init(AST_TYPE_LIT_NUM, ast_num_data_init(-1));
argv[1] = $2;
$$ = ast_init(AST_TYPE_CALL,
ast_call_data_init(

View File

@@ -6,11 +6,12 @@
// The type of an `AST`.
typedef enum {
// Primitive types.
AST_TYPE_NUM, // A number (float).
AST_TYPE_STR, // A string
// Primitive type literals.
AST_TYPE_LIT_NUM, // A number (float) literal.
AST_TYPE_LIT_BOOL, // A boolean literal.
AST_TYPE_LIT_KIND, // A kind literal.
AST_TYPE_INT, // An integer.
AST_TYPE_BOOL, // A boolean.
AST_TYPE_SYM, // A symbol.
AST_TYPE_EXC, // Exception.
@@ -24,7 +25,6 @@ typedef enum {
AST_TYPE_VDEF, // A variable definition.
AST_TYPE_VREF, // A variable reference.
AST_TYPE_BLOCK, // A block of code (scope).
AST_TYPE_FDEF, // A function definition.
AST_TYPE_LAMBDA, // An anonymous function definition.
AST_TYPE_ARG, // A definition argument.
AST_TYPE_MAX = AST_TYPE_ARG,
@@ -60,6 +60,12 @@ ASTBoolData* ast_bool_data_init(int val);
// Destroy an `ASTBoolData`.
void ast_bool_data_destroy(ASTBoolData* bol);
// A kind.
typedef struct {
char* name;
} ASTKindData;
// An exception.
typedef struct ASTEXCDATA {
const char* msg; // The exception message.
@@ -148,19 +154,6 @@ void ast_block_data_destroy(ASTBlockData* block);
// Destroy an `ASTBlockData`.
void ast_block_data_destroy_psv(ASTBlockData* block);
typedef struct {
char* name; // Function name.
ARGS; // Function args.
AST* body; // Function body.
} ASTFDefData;
// Create a new `ASTFDefData`.
ASTFDefData* ast_fdef_data_init(char* name, size_t argc, AST** argv, AST* body);
// Destroy an `ASTFDefData`, recursively.
void ast_fdef_data_destroy(ASTFDefData* fdef);
// Destroy an `ASTFDefData`.
void ast_fdef_data_destroy_psv(ASTFDefData* fdef);
typedef struct {
char* name; // Argument name.
} ASTArgData;

View File

@@ -29,9 +29,6 @@ void ast_vref_print(ASTVrefData*, int i);
// Print an `ASTBlockData`.
void ast_block_print(ASTBlockData*, int i);
// Print an `ASTFDefData`.
void ast_fdef_print(ASTFDefData* fdef, int i);
// Print an `ASTArgData`.
void ast_arg_print(ASTArgData* arg, int i);

View File

@@ -2,6 +2,7 @@
#define GC_H
#include <stdlib.h>
#include <stdbool.h>
// The type a GC can refer to.
typedef enum {

View File

@@ -1,21 +0,0 @@
Primitive Types
- num
- bool
- str
```
fact(x) = ? (x <= 1) 1 : f(x-1) * x
> lambda
fact(5) == 120
> True
```
Ordered collections.
| Name | Homogeneous | Fixed Size | Unique |
|---|---|---|---|
| Collection | No | No | No |
| List | Yes | No | No |
| Array | Yes | Yes | No |
| Group | No | Yes | No |
| Set | Yes | No | Yes |
| Perm | Yes | Yes | Yes |