From 7a8a5bdfa073a77fe751128ad2e71afd8bfc39d0 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Mon, 1 Jan 2024 19:10:02 +0100 Subject: [PATCH] add --diagram-ast option to dump AST to graphviz --- src/cli/main.c | 56 +++- src/lib/compiler/graphviz.c | 544 ++++++++++++++++++++++++++++++++++++ src/lib/compiler/graphviz.h | 5 + src/lib/diagram.c | 59 ++++ src/lib/diagram.h | 4 + 5 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 src/lib/compiler/graphviz.c create mode 100644 src/lib/compiler/graphviz.h create mode 100644 src/lib/diagram.c create mode 100644 src/lib/diagram.h diff --git a/src/cli/main.c b/src/cli/main.c index 67fff71..373f3c0 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -35,13 +35,14 @@ #include #include "../lib/run.h" #include "../lib/runtime.h" +#include "../lib/diagram.h" static void usage(FILE *stream, const char *name) { fprintf(stream, "\n" "Usage:\n" - " $ %s [-h | -o | -p | -H | {-d | -a}] \n", name); + " $ %s ..OPTIONS \n", name); } static void help(FILE *stream, const char *name) @@ -54,12 +55,14 @@ static void help(FILE *stream, const char *name) " -i, --inline Execute a string of code instead of a file\n" " -a, --assembly Specify that the source is bytecode and not noja code\n" " -p, --profile Profile the execution of the source (can't be used with -d)\n" - " -o, --output Specify the output file of -p\n" + " -o, --output Specify the output file of -p or --diagram-ast\n" " -H, --heap Specify the heap size of the runtime\n" + " --diagram-ast Generate a GraphViz view of the AST\n" "\n"); } typedef enum { + Mode_DIAGRAM, Mode_ASSEMBLY, Mode_DEFAULT, Mode_HELP, @@ -97,6 +100,10 @@ int main(int argc, char **argv) mode = Mode_ASSEMBLY; + } else if (!strcmp(argv[i], "--diagram-ast")) { + + mode = Mode_DIAGRAM; + } else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--profile")) { profile = true; @@ -137,6 +144,51 @@ int main(int argc, char **argv) help(stdout, argv[0]); code = 0; break; + + case Mode_DIAGRAM: + { + if (input == NULL) { + fprintf(stderr, "Error: No input file\n"); + usage(stderr, argv[0]); + code = -1; + break; + } + + Error error; + Error_Init(&error); + + char *result; + if (no_file) + result = diagramStringAST(input, &error, NULL); + else + result = diagramFileAST(input, &error, NULL); + + if (!result) { + Error_Print(&error, ErrorType_UNSPECIFIED, stderr); + Error_Free(&error); + code = -1; + break; + } + + FILE *stream; + if (output == NULL) + stream = stdout; + else { + stream = fopen(output, "wb"); + if (!stream) { + fprintf(stderr, "Error: Couldn't open '%s'\n", output); + free(result); + code = -1; + break; + } + } + fwrite(result, 1, strlen(result), stream); + if (stream != stdout) + fclose(stream); + free(result); + } + code = 0; + break; case Mode_DEFAULT: case Mode_ASSEMBLY: diff --git a/src/lib/compiler/graphviz.c b/src/lib/compiler/graphviz.c new file mode 100644 index 0000000..c163c6b --- /dev/null +++ b/src/lib/compiler/graphviz.c @@ -0,0 +1,544 @@ + +/* +--------------------------------------------------------------------------+ +** | _ _ _ | +** | | \ | | (_) | +** | | \| | ___ _ __ _ | +** | | . ` |/ _ \| |/ _` | | +** | | |\ | (_) | | (_| | | +** | |_| \_|\___/| |\__,_| | +** | _/ | | +** | |__/ | +** +--------------------------------------------------------------------------+ +** | Copyright (c) 2022 Francesco Cozzuto | +** +--------------------------------------------------------------------------+ +** | This file is part of The Noja Interpreter. | +** | | +** | The Noja Interpreter is free software: you can redistribute it and/or | +** | modify it under the terms of the GNU General Public License as published | +** | by the Free Software Foundation, either version 3 of the License, or (at | +** | your option) any later version. | +** | | +** | The Noja Interpreter is distributed in the hope that it will be useful, | +** | but WITHOUT ANY WARRANTY; without even the implied warranty of | +** | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | +** | Public License for more details. | +** | | +** | You should have received a copy of the GNU General Public License along | +** | with The Noja Interpreter. If not, see . | +** +--------------------------------------------------------------------------+ +** | WHAT IS THIS FILE? | +** | | +** | This file implements the routines that transform the AST into a list of | +** | bytecodes. The functionalities of this file are exposed through the | +** | `compile` function, that takes as input an `AST` and outputs an | +** | `Executable`. | +** | | +** | The function that does the heavy lifting is `emitInstrForNode` which | +** | walks the tree and writes instructions to the `ExeBuilder`. | +** | | +** | Some semantic errors are catched at this phase, in which case, they are | +** | reported by filling out the `error` structure and aborting. It's also | +** | possible that the compilation fails bacause of internal errors (which | +** | usually means "out of memory"). | +** +--------------------------------------------------------------------------+ +*/ + +#include +#include +#include +#include +#include "../utils/defs.h" +#include "graphviz.h" +#include "ASTi.h" + +typedef struct { + char *data; + size_t size; + size_t used; +} Buffer; + +typedef struct { + Error *error; + Buffer head, body; +} GraphViz; + +static void Buffer_Init(Buffer *buf) +{ + buf->data = NULL; + buf->size = 0; + buf->used = 0; +} + +static void Buffer_Free(Buffer *buf) +{ + free(buf->data); +} + +static void GraphViz_Init(GraphViz *gv, Error *error) +{ + Buffer_Init(&gv->head); + Buffer_Init(&gv->body); + gv->error = error; + gv->error->occurred = false; +} +static void GraphViz_Free(GraphViz *gv) +{ + Buffer_Free(&gv->head); + Buffer_Free(&gv->body); +} + +static bool Buffer_EnsureFreeSpace(Buffer *buf, Error *error, size_t num) +{ + if (buf->used + num > buf->size) { + + // Need to add space + + size_t oldsize = buf->size; + size_t newsize = buf->size * 2; + + if (newsize < oldsize + num) + newsize = oldsize + num; + + void *p = realloc(buf->data, newsize); + if (p == NULL) { + Error_Report(error, ErrorType_INTERNAL, "Out of memory"); + return false; + } + + buf->size = newsize; + buf->data = p; + } + + return true; +} + +static bool Buffer_VAppendF(Buffer *buf, Error *error, const char *fmt, va_list args) +{ + va_list args2; + + // Calculate the string length required to hold + // the evaluated format + va_copy(args2, args); + int required = vsnprintf(NULL, 0, fmt, args2); + va_end(args2); + if (required < 0) { + Error_Report(error, ErrorType_INTERNAL, "Bad format"); + return false; + } + + if (!Buffer_EnsureFreeSpace(buf, error, required+1)) + return false; + + vsnprintf(buf->data + buf->used, buf->size - buf->used, fmt, args); + va_end(args); + + buf->used += required; + return true; +} + +static void GraphViz_AppendHead(GraphViz *gv, const char *fmt, ...) +{ + if (gv->error->occurred) + return; + + va_list args; + va_start(args, fmt); + Buffer_VAppendF(&gv->head, gv->error, fmt, args); + va_end(args); +} + +static void GraphViz_AppendBody(GraphViz *gv, const char *fmt, ...) +{ + if (gv->error->occurred) + return; + + va_list args; + va_start(args, fmt); + Buffer_VAppendF(&gv->body, gv->error, fmt, args); + va_end(args); +} + +static char *GraphViz_Complete(GraphViz *gv, size_t *len) +{ + if (gv->error->occurred) + return NULL; + + char *result; + size_t reslen; + + reslen = gv->head.used + gv->body.used; + result = malloc(reslen+1); + if (!result) { + Error_Report(gv->error, ErrorType_INTERNAL, "Out of memory"); + return NULL; + } + + memcpy(result, gv->head.data, gv->head.used); + memcpy(result + gv->head.used, gv->body.data, gv->body.used); + result[reslen] = '\0'; + + if (len) *len = reslen; + return result; +} + +static void nodeToGraphViz(GraphViz *ctx, Node *node, int *count, char *name); + +static void flattenTupleTree(ExprNode *root, ExprNode *tuple[], int max, int *count) +{ + if(root->kind == EXPR_PAIR) + { + flattenTupleTree((ExprNode*) ((OperExprNode*) root)->head, tuple, max, count); + flattenTupleTree((ExprNode*) ((OperExprNode*) root)->head->next, tuple, max, count); + } + else + { + + if(max == *count) + { + assert(0); // TODO: Do something smart instead + } + + tuple[(*count)++] = root; + } +} + +static void generateName(int *count, char *dst, size_t max) +{ + snprintf(dst, max, "node_%d", ++(*count)); +} + +static const char *exprkind_to_label(ExprKind kind) +{ + switch(kind) + { + case EXPR_NULLABLETYPE: return "Nullable Type"; + case EXPR_SUMTYPE: return "Sum Type"; + case EXPR_NOT: return "!"; + case EXPR_POS: return "+"; + case EXPR_NEG: return "-"; + case EXPR_ADD: return "+"; + case EXPR_SUB: return "-"; + case EXPR_MUL: return "×"; + case EXPR_DIV: return "/"; + case EXPR_MOD: return "%"; + case EXPR_EQL: return "=="; + case EXPR_NQL: return "!="; + case EXPR_LSS: return "<"; + case EXPR_LEQ: return "<="; + case EXPR_GRT: return ">"; + case EXPR_GEQ: return ">="; + case EXPR_AND: return "and"; + case EXPR_OR : return "or"; + case EXPR_ASS: return "="; + case EXPR_ARW: return "->"; + default: + UNREACHABLE; + break; + } + UNREACHABLE; + return "???"; +} + +static void childToGraphViz(GraphViz *ctx, Node *child, int *count, char *parent) +{ + char name2[128]; + generateName(count, name2, sizeof(name2)); + nodeToGraphViz(ctx, child, count, name2); + GraphViz_AppendBody(ctx, "\t%s -> %s;\n", parent, name2); +} + +static void funcExprToGraphViz(GraphViz *ctx, FuncExprNode *func, char *func_name, int *count, char *name) +{ + GraphViz_AppendHead(ctx, "\t%s [label=\"%s\"];\n", name, func_name); + + char name2[128]; + generateName(count, name2, sizeof(name2)); + GraphViz_AppendHead(ctx, "\t%s [label=\"args\"];\n", name2); + GraphViz_AppendBody(ctx, "\t%s -> %s;\n", name, name2); + + ArgumentNode *arg = (ArgumentNode*) func->argv; + while(arg) + { + char name3[128]; + generateName(count, name3, sizeof(name3)); + GraphViz_AppendHead(ctx, "\t%s [label=\"%s\"];\n", name3, arg->name); + GraphViz_AppendBody(ctx, "\t%s -> %s;\n", name2, name3); + if (arg->value != NULL) childToGraphViz(ctx, arg->value, count, name3); + if (arg->type != NULL) childToGraphViz(ctx, arg->type, count, name3); + arg = (ArgumentNode*) arg->base.next; + } + + childToGraphViz(ctx, func->body, count, name); +} + +static void exprToGraphViz(GraphViz *ctx, ExprNode *expr, int *count, char *name) +{ + switch(expr->kind) + { + case EXPR_PAIR: + UNREACHABLE; + return; // For the compiler warning. + + case EXPR_NULLABLETYPE: + case EXPR_SUMTYPE: + case EXPR_ASS: case EXPR_ARW: + case EXPR_NOT: case EXPR_MOD: + case EXPR_POS: case EXPR_NEG: + case EXPR_ADD: case EXPR_SUB: + case EXPR_MUL: case EXPR_DIV: + case EXPR_EQL: case EXPR_NQL: + case EXPR_LSS: case EXPR_LEQ: + case EXPR_GRT: case EXPR_GEQ: + case EXPR_AND: case EXPR_OR: + { + OperExprNode *oper = (OperExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"%s\"];\n", name, exprkind_to_label(expr->kind)); + for(Node *operand = oper->head; operand; operand = operand->next) + childToGraphViz(ctx, operand, count, name); + return; + } + + case EXPR_INT: + { + IntExprNode *p = (IntExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"%d\"];\n", name, p->val); + return; + } + + case EXPR_FLOAT: + { + FloatExprNode *p = (FloatExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"%g\"];\n", name, p->val); + return; + } + + case EXPR_STRING: + { + StringExprNode *p = (StringExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"", name); + char *s = p->val; + size_t i = 0; + size_t len = strlen(s); + while (i < len) { + + size_t start = i; + while (i < len && (s[i] != '"' && s[i] != '\\' && s[i] != '\n' && s[i] != '\t')) + i++; + size_t end = i; + GraphViz_AppendHead(ctx, "%.*s", end - start, s + start); + if (i == len) + break; + switch (s[i]) { + case '"': GraphViz_AppendHead(ctx, "\\\""); break; + case '\\': GraphViz_AppendHead(ctx, "\\\\"); break; + case '\t': GraphViz_AppendHead(ctx, "\\t"); break; + case '\n': GraphViz_AppendHead(ctx, "\\n"); break; + } + i++; + } + GraphViz_AppendHead(ctx, "\"];\n"); + return; + } + + case EXPR_IDENT: + { + IdentExprNode *p = (IdentExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"%s\"];\n", name, p->val); + return; + } + + case EXPR_LIST: + { + ListExprNode *l = (ListExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"List\"];\n", name); + for (Node *item = l->items; item; item = item->next) + childToGraphViz(ctx, item, count, name); + return; + } + + case EXPR_MAP: + { + MapExprNode *m = (MapExprNode*) expr; + + GraphViz_AppendHead(ctx, "\t%s [label=\"Map\"];\n", name); + + Node *key = m->keys; + Node *item = m->items; + + while(item) + { + char name2[128]; + generateName(count, name2, sizeof(name2)); + GraphViz_AppendHead(ctx, "\t%s [label=\"Entry\"];\n", name2); + GraphViz_AppendBody(ctx, "\t%s -> %s;\n", name, name2); + + childToGraphViz(ctx, key, count, name2); + childToGraphViz(ctx, item, count, name2); + + key = key->next; + item = item->next; + } + return; + } + + case EXPR_CALL: + { + CallExprNode *call = (CallExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"Call\"];\n", name); + + childToGraphViz(ctx, call->func, count, name); + + Node *arg = call->argv; + while(arg) + { + childToGraphViz(ctx, arg, count, name); + arg = arg->next; + } + } + return; + + case EXPR_FUNC: + funcExprToGraphViz(ctx, (FuncExprNode*) expr, "???", count, name); + return; + + case EXPR_SELECT: + { + IndexSelectionExprNode *sel = (IndexSelectionExprNode*) expr; + GraphViz_AppendHead(ctx, "\t%s [label=\"Select\"];\n", name); + childToGraphViz(ctx, sel->set, count, name); + childToGraphViz(ctx, sel->idx, count, name); + return; + } + + case EXPR_NONE: + GraphViz_AppendHead(ctx, "\t%s [label=\"none\"];\n", name); + return; + + case EXPR_TRUE: + GraphViz_AppendHead(ctx, "\t%s [label=\"true\"];\n", name); + return; + + case EXPR_FALSE: + GraphViz_AppendHead(ctx, "\t%s [label=\"false\"];\n", name); + return; + + default: + UNREACHABLE; + break; + } +} + +static void nodeToGraphViz(GraphViz *ctx, Node *node, int *count, char *name) +{ + assert(node != NULL); + + switch(node->kind) + { + case NODE_EXPR: + exprToGraphViz(ctx, (ExprNode*) node, count, name); + return; + + case NODE_BREAK: + GraphViz_AppendHead(ctx, "\t%s [label=\"break\"];\n", name); + return; + + case NODE_IFELSE: + { + IfElseNode *ifelse = (IfElseNode*) node; + + if (ifelse->false_branch) + GraphViz_AppendHead(ctx, "\t%s [label=\"if-else\"];\n", name); + else + GraphViz_AppendHead(ctx, "\t%s [label=\"if\"];\n", name); + + childToGraphViz(ctx, ifelse->condition, count, name); + childToGraphViz(ctx, ifelse->true_branch, count, name); + if (ifelse->false_branch) + childToGraphViz(ctx, ifelse->false_branch, count, name); + } + return; + + case NODE_WHILE: + { + WhileNode *while_ = (WhileNode*) node; + GraphViz_AppendHead(ctx, "\t%s [label=\"while\"];\n", name); + childToGraphViz(ctx, while_->condition, count, name); + childToGraphViz(ctx, while_->body, count, name); + } + return; + + case NODE_DOWHILE: + { + DoWhileNode *dowhile = (DoWhileNode*) node; + GraphViz_AppendHead(ctx, "\t%s [label=\"do-while\"];\n", name); + childToGraphViz(ctx, dowhile->condition, count, name); + childToGraphViz(ctx, dowhile->body, count, name); + } + return; + + case NODE_COMP: + { + CompoundNode *comp = (CompoundNode*) node; + + GraphViz_AppendHead(ctx, "\t%s [label=\"compound\"];\n", name); + + Node *stmt = comp->head; + while(stmt) + { + childToGraphViz(ctx, stmt, count, name); + stmt = stmt->next; + } + return; + } + + case NODE_RETURN: + { + ReturnNode *ret = (ReturnNode*) node; + + GraphViz_AppendHead(ctx, "\t%s [label=\"return\"];\n", name); + + ExprNode *tuple[32]; + int return_count = 0; + + flattenTupleTree((ExprNode*) ret->val, tuple, sizeof(tuple)/sizeof(tuple[0]), &return_count); + + for(int i = 0; i < return_count; i += 1) + childToGraphViz(ctx, (Node*) tuple[i], count, name); + return; + } + + case NODE_FUNC: + { + FuncDeclNode *func = (FuncDeclNode*) node; + GraphViz_AppendHead(ctx, "\t%s [label=\"func\"];\n", name); + funcExprToGraphViz(ctx, func->expr, func->name->val, count, name); + } + return; + + default: + UNREACHABLE; + } + UNREACHABLE; +} + +char *graphviz(AST *ast, Error *err, size_t *len) +{ + err->occurred = false; + + GraphViz gv; + GraphViz_Init(&gv, err); + GraphViz_AppendHead(&gv, "digraph G {\n"); + + int count = 0; + char name[128]; + generateName(&count, name, sizeof(name)); + nodeToGraphViz(&gv, ast->root, &count, name); + + GraphViz_AppendHead(&gv, "\n"); + GraphViz_AppendBody(&gv, "}\n"); + + char *res = GraphViz_Complete(&gv, len); + + GraphViz_Free(&gv); + return res; +} diff --git a/src/lib/compiler/graphviz.h b/src/lib/compiler/graphviz.h new file mode 100644 index 0000000..21b0f07 --- /dev/null +++ b/src/lib/compiler/graphviz.h @@ -0,0 +1,5 @@ + +#include "AST.h" +#include "../utils/error.h" + +char *graphviz(AST *ast, Error *err, size_t *len); \ No newline at end of file diff --git a/src/lib/diagram.c b/src/lib/diagram.c new file mode 100644 index 0000000..d536884 --- /dev/null +++ b/src/lib/diagram.c @@ -0,0 +1,59 @@ +#include +#include "diagram.h" +#include "utils/source.h" +#include "utils/bpalloc.h" +#include "compiler/parse.h" +#include "compiler/graphviz.h" + +static char *diagramSourceAST(Source *source, Error *error, + int *error_offset) +{ + BPAlloc *alloc = BPAlloc_Init(-1); + if(alloc == NULL) + { + *error_offset = -1; + Error_Report(error, ErrorType_INTERNAL, "No memory"); + return NULL; + } + + // NOTE: The AST is stored in the BPAlloc. Its + // lifetime is the same as the pool. + AST *ast = parse(source, alloc, error, error_offset); + if(ast == NULL) + { + assert(error->occurred); + BPAlloc_Free(alloc); + return NULL; + } + + char *out = graphviz(ast, error, NULL); + + BPAlloc_Free(alloc); + return out; +} + +char *diagramFileAST(const char *file, Error *error, + int *error_offset) +{ + Source *source = Source_FromFile(file, error); + if (source == NULL) + return NULL; + + char *out = diagramSourceAST(source, error, error_offset); + + Source_Free(source); + return out; +} + +char *diagramStringAST(const char *str, Error *error, + int *error_offset) +{ + Source *source = Source_FromString(NULL, str, -1, error); + if (source == NULL) + return NULL; + + char *out = diagramSourceAST(source, error, error_offset); + + Source_Free(source); + return out; +} \ No newline at end of file diff --git a/src/lib/diagram.h b/src/lib/diagram.h new file mode 100644 index 0000000..1095f2b --- /dev/null +++ b/src/lib/diagram.h @@ -0,0 +1,4 @@ +#include "utils/error.h" + +char *diagramFileAST(const char *file, Error *error, int *error_offset); +char *diagramStringAST(const char *str, Error *error, int *error_offset); \ No newline at end of file