first commit

This commit is contained in:
cozis
2021-10-31 04:17:57 +00:00
commit 7a5d0d1dcb
55 changed files with 5204 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
#ifndef AST_H
#define AST_H
typedef struct xAST AST;
#endif
+73
View File
@@ -0,0 +1,73 @@
#ifndef ASTi_H
#define ASTi_H
#include "../utils/source.h"
#include "AST.h"
typedef enum {
NODE_EXPR,
} NodeKind;
typedef enum {
EXPR_POS,
EXPR_NEG,
EXPR_ADD,
EXPR_SUB,
EXPR_MUL,
EXPR_DIV,
EXPR_INT,
EXPR_FLOAT,
EXPR_STRING,
EXPR_IDENT,
} ExprKind;
typedef struct Node Node;
struct xAST {
Source *src;
Node *root;
};
struct Node {
NodeKind kind;
Node *next;
int offset,
length;
char body[];
};
typedef struct {
Node base;
ExprKind kind;
char body[];
} ExprNode;
typedef struct {
ExprNode base;
long long int val;
} IntExprNode;
typedef struct {
ExprNode base;
double val;
} FloatExprNode;
typedef struct {
ExprNode base;
char *val;
int len;
} StringExprNode;
typedef struct {
ExprNode base;
char *val;
int len;
} IdentExprNode;
typedef struct {
ExprNode base;
Node *head;
int count;
} OperExprNode;
#endif
+123
View File
@@ -0,0 +1,123 @@
#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include "../utils/defs.h"
#include "compile.h"
#include "ASTi.h"
static Opcode exprkind_to_opcode(ExprKind kind)
{
switch(kind)
{
case EXPR_POS: return OPCODE_POS;
case EXPR_NEG: return OPCODE_NEG;
case EXPR_ADD: return OPCODE_ADD;
case EXPR_SUB: return OPCODE_SUB;
case EXPR_MUL: return OPCODE_MUL;
case EXPR_DIV: return OPCODE_DIV;
default:
UNREACHABLE;
break;
}
}
static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error)
{
assert(node != NULL);
switch(node->kind)
{
case NODE_EXPR:
{
ExprNode *expr = (ExprNode*) node;
switch(expr->kind)
{
case EXPR_POS:
case EXPR_NEG:
case EXPR_ADD:
case EXPR_SUB:
case EXPR_MUL:
case EXPR_DIV:
{
OperExprNode *oper = (OperExprNode*) expr;
for(Node *operand = oper->head; operand; operand = operand->next)
emit_instr_for_node(exeb, operand, error);
return ExeBuilder_Append(exeb, error,
exprkind_to_opcode(expr->kind),
NULL, 0, node->offset, node->length);
}
case EXPR_INT:
{
IntExprNode *p = (IntExprNode*) expr;
Operand op = { .type = OPTP_INT, .as_int = p->val };
return ExeBuilder_Append(exeb, error, OPCODE_PUSHI, &op, 1, node->offset, node->length);
}
case EXPR_FLOAT:
{
FloatExprNode *p = (FloatExprNode*) expr;
Operand op = { .type = OPTP_FLOAT, .as_float = p->val };
return ExeBuilder_Append(exeb, error, OPCODE_PUSHF, &op, 1, node->offset, node->length);
}
case EXPR_STRING:
{
StringExprNode *p = (StringExprNode*) expr;
Operand op = { .type = OPTP_STRING, .as_string = p->val };
return ExeBuilder_Append(exeb, error, OPCODE_PUSHS, &op, 1, node->offset, node->length);
}
case EXPR_IDENT:
{
IdentExprNode *p = (IdentExprNode*) expr;
Operand op = { .type = OPTP_STRING, .as_string = p->val };
return ExeBuilder_Append(exeb, error, OPCODE_PUSHV, &op, 1, node->offset, node->length);
}
default:
UNREACHABLE;
break;
}
break;
}
default:
UNREACHABLE;
break;
}
}
Executable *compile(AST *ast, BPAlloc *alloc, Error *error)
{
assert(ast != NULL);
assert(error != NULL);
BPAlloc *alloc2 = alloc;
if(alloc2 == NULL)
{
alloc2 = BPAlloc_Init(-1);
if(alloc2 == NULL)
return NULL;
}
Executable *exe = NULL;
ExeBuilder *exeb = ExeBuilder_New(alloc2);
if(exeb)
{
if(!emit_instr_for_node(exeb, ast->root, error))
return 0;
if(ExeBuilder_Append(exeb, error, OPCODE_RETURN, NULL, 0, Source_GetSize(ast->src), 0))
exe = ExeBuilder_Finalize(exeb, error);
}
if(alloc == NULL)
BPAlloc_Free(alloc2);
return exe;
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef COMPILE_H
#define COMPILE_H
#include "../utils/error.h"
#include "../utils/bpalloc.h"
#include "../common/executable.h"
#include "AST.h"
Executable *compile(AST *ast, BPAlloc *alloc, Error *error);
#endif
+762
View File
@@ -0,0 +1,762 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include "parse.h"
#include "ASTi.h"
#define UNREACHABLE assert(0)
#ifndef NULL
#define NULL ((void*) 0)
#endif
typedef enum {
TPOS = '+',
TNEG = '-',
TADD = '+',
TSUB = '-',
TMUL = '*',
TDIV = '/',
TASS = '=',
TDONE = 256,
TINT,
TFLOAT,
TSTRING,
TIDENT,
} TokenKind;
typedef struct Token Token;
struct Token {
TokenKind kind;
Token *prev, *next;
int offset, length;
};
typedef struct {
const char *src;
Token *token;
BPAlloc *alloc;
Error *error;
} Context;
static Node *parse_statement(Context *ctx);
static Node *parse_expression(Context *ctx);
static Node *parse_expression_statement(Context *ctx);
static inline _Bool isoper(char c)
{
return c == '+' ||
c == '-' ||
c == '*' ||
c == '/' ||
c == '=';
}
AST *parse(Source *src, BPAlloc *alloc, Error *error)
{
assert(src != NULL);
assert(alloc != NULL);
const char *str = Source_GetBody(src);
int len = Source_GetSize(src);
assert(str != NULL);
assert(len >= 0);
AST *ast = BPAlloc_Malloc(alloc, sizeof(AST));
if(ast == NULL)
return NULL;
Token *head = NULL,
*tail = NULL;
int i = 0;
while(1)
{
// Skip whitespace and comments.
while(i < len && (isspace(str[i]) || str[i] == '#'))
{
while(i < len && isspace(str[i]))
i += 1;
if(str[i] == '#')
{
i += 1;
while(i < len && str[i] != '\n')
i += 1;
}
}
if(i == len)
break; // No more tokens left.
Token *tok = BPAlloc_Malloc(alloc, sizeof(Token)); // Allocate a token.
if(tok == NULL)
{
// Error: No memory.
Error_Report(error, 1, "No memory");
return NULL;
}
if(isalpha(str[i]) || str[i] == '_')
{
tok->kind = TIDENT;
tok->offset = i;
while(i < len && (isalpha(str[i]) || str[i] == '_'))
i += 1;
tok->length = i - tok->offset;
}
else if(isdigit(str[i]))
{
tok->kind = TINT;
tok->offset = i;
while(i < len && isdigit(str[i]))
i += 1;
if(i+1 < len && str[i] == '.' && isdigit(str[i+1]))
{
i += 1; // Consume the dot.
tok->kind = TFLOAT;
while(i < len && isdigit(str[i]))
i += 1;
}
tok->length = i - tok->offset;
}
else if(str[i] == '\'' || str[i] == '"')
{
tok->kind = TSTRING;
tok->offset = i;
char f = str[i];
i += 1; // Skip the starting quote.
while(i < len && str[i] != f)
i += 1;
if(i == len)
{
Error_Report(error, 0, "Source ended inside string literal");
return NULL;
}
i += 1; // Consume the ' or ".
tok->length = i - tok->offset;
}
else if(isoper(str[i]))
{
tok->offset = i;
while(i < len && isoper(str[i]))
i += 1;
tok->length = i - tok->offset;
// Determine the token
#define matchop(str, len, const_str) \
(len == sizeof(const_str)-1 && !strncmp(str, const_str, sizeof(const_str)-1))
if(matchop(str + tok->offset, tok->length, "+"))
{
tok->kind = TADD;
}
else if(matchop(str + tok->offset, tok->length, "-"))
{
tok->kind = TSUB;
}
else if(matchop(str + tok->offset, tok->length, "*"))
{
tok->kind = TMUL;
}
else if(matchop(str + tok->offset, tok->length, "/"))
{
tok->kind = TDIV;
}
else if(matchop(str + tok->offset, tok->length, "="))
{
tok->kind = TASS;
}
else
{
// Not a known operator.
tok->kind = str[tok->offset];
tok->length = 1;
i = tok->offset + 1;
}
#undef matchop
}
else
{
tok->kind = str[i];
tok->offset = i;
tok->length = 1;
i += 1;
}
// Append to the token list.
if(head)
tail->next = tok;
else
head = tok;
tok->prev = tail;
tok->next = NULL;
tail = tok;
}
{
Token *tok = BPAlloc_Malloc(alloc, sizeof(Token)); // Allocate a token.
if(tok == NULL)
{
// Error: No memory.
Error_Report(error, 1, "No memory");
return NULL;
}
tok->kind = TDONE;
tok->offset = i;
tok->length = 0;
if(head)
tail->next = tok;
else
head = tok;
tok->prev = tail;
tok->next = NULL;
tail = tok;
}
Context ctx;
ctx.src = str;
ctx.token = head;
ctx.alloc = alloc;
ctx.error = error;
Node *root = parse_statement(&ctx);
if(root == NULL)
return NULL;
ast->src = src; // Not copying! Be sure to not free the source before the AST!
ast->root = root;
if(ast->src == NULL)
return NULL;
return ast;
}
static Node *parse_statement(Context *ctx)
{
assert(ctx != NULL);
switch(ctx->token->kind)
{
case '*':
case '/':
case TASS:
case TDONE:
UNREACHABLE;
break;
case '(':
case '+':
case '-':
case TINT:
case TFLOAT:
case TSTRING:
case TIDENT:
return parse_expression_statement(ctx);
}
Error_Report(ctx->error, 0, "Got token \"%.*s\" where the start of a statement was expected",
ctx->token->length, ctx->src + ctx->token->offset);
return NULL;
}
static Node *parse_expression_statement(Context *ctx)
{
assert(ctx != NULL);
Node *expr = parse_expression(ctx);
if(expr == NULL)
return NULL;
assert(ctx->token != NULL);
if(ctx->token->kind == TDONE)
// If the source ended before
// the final ";", it's ok.
return expr;
if(ctx->token->kind != ';')
{
// ERROR: Got something other than
// a semicolon at the end of statement.
Error_Report(ctx->error, 0, "Got token \"%.*s\" where \";\" was expected", ctx->token->length, ctx->src + ctx->token->offset);
return NULL;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return expr;
}
static Node *parse_string_primary_expression(Context *ctx)
{
assert(ctx != NULL);
assert(ctx->token->kind == TSTRING);
const char *src = ctx->src;
int len = ctx->token->offset + ctx->token->length - 1;
int i = ctx->token->offset + 1;
assert(src[i-1] == '"' || src[i-1] == '\'');
assert(src[len] == '"' || src[len] == '\'');
char temp[4096];
int temp_used = 0;
do
{
int segm_off, segm_len;
{
segm_off = i;
while(i < len && src[i] != '\\')
i += 1;
segm_len = i - segm_off;
}
if(temp_used + segm_len >= (int) sizeof(temp))
{
Error_Report(ctx->error, 1, "String is too big to be rendered inside the fixed size buffer");
return NULL;
}
memcpy(temp + temp_used, src + segm_off, segm_len);
temp_used += segm_len;
if(src[i] == '\\')
{
i += 1; // Consume the \.
if(temp_used + 1 >= (int) sizeof(temp))
{
Error_Report(ctx->error, 1, "String is too big to be rendered inside the fixed size buffer");
return NULL;
}
if(i == len)
{
// Append the \ as a normal char.
temp[temp_used++] = '\\';
}
else
{
switch(src[i])
{
case 'n': temp[temp_used++] = '\n'; break;
case 't': temp[temp_used++] = '\t'; break;
case 'r': temp[temp_used++] = '\r'; break;
default:
Error_Report(ctx->error, 0, "Invalid escape sequence \\%c", src[i]);
return NULL;
}
i += 1; // Consume the char after the \.
}
}
}
while(i < len);
assert(temp_used < (int) sizeof(temp));
temp[temp_used] = '\0';
char *copy;
int copyl;
{
copy = BPAlloc_Malloc(ctx->alloc, temp_used + 1);
if(copy == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
strcpy(copy, temp);
copyl = temp_used;
}
StringExprNode *node;
{
node = BPAlloc_Malloc(ctx->alloc, sizeof(StringExprNode));
if(node == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
node->base.base.kind = NODE_EXPR;
node->base.base.next = NULL;
node->base.base.offset = ctx->token->offset;
node->base.base.length = ctx->token->length;
node->base.kind = EXPR_STRING;
node->val = copy;
node->len = copyl;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return (Node*) node;
}
static Node *parse_primary_expresion(Context *ctx)
{
assert(ctx != NULL);
switch(ctx->token->kind)
{
case '+':
case '-':
{
Token *operator = ctx->token;
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
Node *operand = parse_primary_expresion(ctx);
if(operand == NULL)
return NULL;
OperExprNode *temp = BPAlloc_Malloc(ctx->alloc, sizeof(OperExprNode));
{
if(temp == NULL)
return NULL;
temp->base.base.kind = NODE_EXPR;
temp->base.base.next = NULL;
temp->base.base.offset = operator->offset;
temp->base.base.length = operand->offset + operand->length - operator->offset;
temp->base.kind = operator->kind == '+' ? EXPR_POS : EXPR_NEG;
temp->head = operand;
temp->count = 1;
}
return (Node*) temp;
}
case '(':
{
ctx->token = ctx->token->next;
Node *node = parse_expression(ctx);
if(node == NULL)
return NULL;
if(ctx->token->kind != ')')
{
Error_Report(ctx->error, 0, "Missing \")\", after sub-expression");
return NULL;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return node;
}
case TINT:
{
char buffer[64];
if(ctx->token->length >= (int) sizeof(buffer))
{
Error_Report(ctx->error, 1, "Integer is too big");
return NULL;
}
memcpy(buffer, ctx->src + ctx->token->offset, ctx->token->length);
buffer[ctx->token->length] = '\0';
errno = 0;
long long int val = strtoll(buffer, NULL, 10);
if(errno == ERANGE)
{
Error_Report(ctx->error, 1, "Integer is too big");
return NULL;
}
else assert(errno == 0);
IntExprNode *node;
{
node = BPAlloc_Malloc(ctx->alloc, sizeof(IntExprNode));
if(node == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
node->base.base.kind = NODE_EXPR;
node->base.base.next = NULL;
node->base.base.offset = ctx->token->offset;
node->base.base.length = ctx->token->length;
node->base.kind = EXPR_INT;
node->val = val;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return (Node*) node;
}
case TFLOAT:
{
char buffer[64];
if(ctx->token->length >= (int) sizeof(buffer))
{
Error_Report(ctx->error, 1, "Floating is too big");
return NULL;
}
memcpy(buffer, ctx->src + ctx->token->offset, ctx->token->length);
buffer[ctx->token->length] = '\0';
errno = 0;
double val = strtod(buffer, NULL);
if(errno == ERANGE)
{
Error_Report(ctx->error, 1, "Floating is too big");
return NULL;
}
else assert(errno == 0);
FloatExprNode *node;
{
node = BPAlloc_Malloc(ctx->alloc, sizeof(FloatExprNode));
if(node == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
node->base.base.kind = NODE_EXPR;
node->base.base.next = NULL;
node->base.base.offset = ctx->token->offset;
node->base.base.length = ctx->token->length;
node->base.kind = EXPR_FLOAT;
node->val = val;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return (Node*) node;
}
case TSTRING:
return parse_string_primary_expression(ctx);
case TIDENT:
{
char *copy;
int copyl;
{
copy = BPAlloc_Malloc(ctx->alloc, ctx->token->length + 1);
if(copy == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
copyl = ctx->token->length;
memcpy(copy, ctx->src + ctx->token->offset, copyl);
copy[copyl] = '\0';
}
IdentExprNode *node;
{
node = BPAlloc_Malloc(ctx->alloc, sizeof(IdentExprNode));
if(node == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
node->base.base.kind = NODE_EXPR;
node->base.base.next = NULL;
node->base.base.offset = ctx->token->offset;
node->base.base.length = ctx->token->length;
node->base.kind = EXPR_IDENT;
node->val = copy;
node->len = copyl;
}
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
return (Node*) node;
}
case TDONE:
Error_Report(ctx->error, 1, "Unexpected end of source where a primary expression was expected");
return NULL;
default:
Error_Report(ctx->error, 1, "Unexpected token \"%.*s\" where a primary expression was expected", ctx->token->length, ctx->src + ctx->token->offset);
return NULL;
}
UNREACHABLE;
return NULL;
}
static inline _Bool isbinop(Token *tok)
{
assert(tok != NULL);
return tok->kind == '+' ||
tok->kind == '-' ||
tok->kind == '*' ||
tok->kind == '/';
}
static inline _Bool isrightassoc(Token *tok)
{
assert(tok != NULL);
(void) tok;
return 0;
}
static inline int precedenceof(Token *tok)
{
assert(tok != NULL);
switch(tok->kind)
{
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return -10000000;
}
UNREACHABLE;
return -100000000;
}
static Node *parse_expression_2(Context *ctx, Node *left_expr, int min_prec)
{
while(isbinop(ctx->token) && precedenceof(ctx->token) >= min_prec)
{
Token *op = ctx->token;
ctx->token = ctx->token->next;
assert(ctx->token != NULL);
Node *right_expr = parse_primary_expresion(ctx);
if(right_expr == NULL)
return NULL;
while(isbinop(ctx->token) && (precedenceof(ctx->token) > precedenceof(op) || (precedenceof(ctx->token) == precedenceof(op) && isrightassoc(ctx->token))))
{
right_expr = parse_expression_2(ctx, right_expr, precedenceof(op) + 1);
if(right_expr == NULL)
return NULL;
}
OperExprNode *temp;
{
temp = BPAlloc_Malloc(ctx->alloc, sizeof(OperExprNode));
if(temp == NULL)
{
Error_Report(ctx->error, 1, "No memory");
return NULL;
}
temp->base.base.kind = NODE_EXPR;
temp->base.base.next = NULL;
temp->base.base.offset = left_expr->offset;
temp->base.base.length = right_expr->offset + right_expr->length - left_expr->offset;
switch(op->kind)
{
case '+': temp->base.kind = EXPR_ADD; break;
case '-': temp->base.kind = EXPR_SUB; break;
case '*': temp->base.kind = EXPR_MUL; break;
case '/': temp->base.kind = EXPR_DIV; break;
default:
UNREACHABLE;
break;
}
temp->head = left_expr;
temp->head->next = right_expr;
temp->count = 2;
assert(right_expr->next == NULL);
}
left_expr = (Node*) temp;
}
return left_expr;
}
static Node *parse_expression(Context *ctx)
{
Node *left_expr = parse_primary_expresion(ctx);
if(left_expr == NULL)
return NULL;
if(ctx->token->kind == TDONE)
return left_expr;
return parse_expression_2(ctx, left_expr, -1000000000);
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef PARSE_H
#define PARSE_H
#include "../utils/bpalloc.h"
#include "../utils/source.h"
#include "../utils/error.h"
#include "AST.h"
AST *parse(Source *src, BPAlloc *alloc, Error *error);
#endif
+161
View File
@@ -0,0 +1,161 @@
#include <assert.h>
#include <xjson.h>
#include "serialize.h"
#include "ASTi.h"
#define UNREACHABLE assert(0)
static xj_value *convert_node(Node *node, xj_error *error, xj_alloc **alloc);
char *serialize(AST *ast, int *len)
{
xj_alloc *alloc = NULL;
xj_error error;
xj_value *value = convert_node(ast->root, &error, &alloc);
if(value == NULL)
{
xj_free(alloc);
return NULL;
}
else
{
char *serialized = xj_encode(value, len);
xj_free(alloc);
return serialized;
}
}
static const char *expr_kind_name(ExprNode *expr)
{
switch(expr->kind)
{
case EXPR_POS: return "pos";
case EXPR_NEG: return "neg";
case EXPR_ADD: return "add";
case EXPR_SUB: return "sub";
case EXPR_MUL: return "mul";
case EXPR_DIV: return "div";
case EXPR_INT: return "int";
case EXPR_FLOAT: return "float";
case EXPR_STRING: return "string";
case EXPR_IDENT: return "ident";
default:
UNREACHABLE;
return NULL;
}
UNREACHABLE;
return NULL;
}
static xj_value *convert_node_list(Node *head, xj_error *error, xj_alloc **alloc);
static xj_value *convert_node(Node *node, xj_error *error, xj_alloc **alloc)
{
switch(node->kind)
{
case NODE_EXPR:
ExprNode *expr = (ExprNode*) node;
switch(expr->kind)
{
case EXPR_POS:
case EXPR_NEG:
case EXPR_ADD:
case EXPR_SUB:
case EXPR_MUL:
case EXPR_DIV:
{
OperExprNode *oper = (OperExprNode*) expr;
xj_value *operands = NULL;
if(oper->count > 0)
{
operands = convert_node_list((Node*) oper->head, error, alloc);
if(operands == NULL)
return NULL;
}
return xj_decodef(error, alloc,
"{"
"\t\"node-kind\": \"expr\", \n"
"\t\"expr-kind\": %Q, \n"
"\t\"operands\": %L\n"
"}", expr_kind_name(expr), operands);
}
case EXPR_INT:
return xj_decodef(error, alloc,
"{"
"\t\"node-kind\": \"expr\", \n"
"\t\"expr-kind\": %Q, \n"
"\t\"value\": %d\n"
"}", expr_kind_name(expr), ((IntExprNode*) expr)->val);
case EXPR_FLOAT:
return xj_decodef(error, alloc,
"{"
"\t\"node-kind\": \"expr\", \n"
"\t\"expr-kind\": %Q, \n"
"\t\"value\": %f\n"
"}", expr_kind_name(expr), ((FloatExprNode*) expr)->val);
case EXPR_STRING:
return xj_decodef(error, alloc,
"{"
"\t\"node-kind\": \"expr\", \n"
"\t\"expr-kind\": %Q, \n"
"\t\"value\": %Q\n"
"}", expr_kind_name(expr), ((StringExprNode*) expr)->val);
case EXPR_IDENT:
return xj_decodef(error, alloc,
"{"
"\t\"node-kind\": \"expr\", \n"
"\t\"expr-kind\": %Q, \n"
"\t\"value\": %Q\n"
"}", expr_kind_name(expr), ((IdentExprNode*) expr)->val);
default:
UNREACHABLE;
break;
}
break;
default:
UNREACHABLE;
break;
}
UNREACHABLE;
return NULL;
}
static xj_value *convert_node_list(Node *head, xj_error *error, xj_alloc **alloc)
{
xj_value *json_head = NULL;
xj_value **json_tail = &json_head;
Node *curs = head;
while(curs)
{
xj_value *temp = convert_node(curs, error, alloc);
if(temp == NULL)
return NULL;
temp->next = NULL;
*json_tail = temp;
json_tail = &temp->next;
curs = curs->next;
}
return json_head;
}
+5
View File
@@ -0,0 +1,5 @@
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include "AST.h"
char *serialize(AST *ast, int *len);
#endif