commit 04464ec2e7e0c482cd22350551663ec89a1c9e73 Author: Arthur Barraux Date: Mon Jun 2 10:27:04 2025 +0200 first commit diff --git a/include/config_tools.h b/include/config_tools.h new file mode 100644 index 0000000..be87dba --- /dev/null +++ b/include/config_tools.h @@ -0,0 +1,34 @@ +#ifndef CONFIG_TOOLS_H_ +#define CONFIG_TOOLS_H_ + +#include "data.h" +#include "parser.h" +#include + +config_t *config_create(void); +void free_node(node_t *node); +void config_destroy(config_t *config); +int handle_map_key(config_t *config, node_t **args, int arg_count); +int handle_define(config_t *config, node_t **args, int arg_count); +int execute_function_call(config_t *config, node_t *call); +int config_parse_string(config_t *config, const char *input); +int config_parse_file(config_t *config, const char *filename); + +const char *config_get_key_mapping(config_t *config, const char *key_combo); +node_t *find_variable(config_t *config, const char *name); + +const char *config_get_string(config_t *config, const char *path, + const char *default_value); +int config_get_int(config_t *config, const char *path, int default_value); +double config_get_double(config_t *config, const char *path, + double default_value); +bool config_get_bool(config_t *config, const char *path, bool default_value); +void config_print_all(config_t *config); + +static struct { + const char *name; + int (*handler)(config_t *config, node_t **args, int arg_count); +} builtin_functions[] = { + {"map-key", handle_map_key}, {"define", handle_define}, {NULL, NULL}}; + +#endif diff --git a/include/data.h b/include/data.h new file mode 100644 index 0000000..fccb8c4 --- /dev/null +++ b/include/data.h @@ -0,0 +1,100 @@ +#ifndef DATA_H_PARSER +#define DATA_H_PARSER + +#include "define.h" +#include +#include +#include +#include +#include + +// Token types for lexical analysis + +typedef enum { + TOKEN_COMMA, // , + TOKEN_PERCENT, // % + TOKEN_LPAREN, // ( + TOKEN_RPAREN, // ) + TOKEN_SYMBOL, // identifiers/function names + TOKEN_STRING, // "quoted strings" + TOKEN_NUMBER, // integers and floats + TOKEN_BOOLEAN, // true false + TOKEN_NEWLINE, // \n (statement separator) + TOKEN_EOF, + TOKEN_ERROR +} token_type_t; + +typedef struct { + token_type_t type; + char value[MAX_TOKEN_LENGTH]; + int line; + int column; +} token_t; + +// AST node types +typedef enum { + NODE_SYMBOL, + NODE_STRING, + NODE_NUMBER, + NODE_BOOLEAN, + NODE_FUNCTION_REF, // %function-name + NODE_FUNCTION_CALL // ,function-name() +} node_type_t; + +typedef struct node { + node_type_t type; + union { + char symbol[MAX_SYMBOL_LENGTH]; + char string[MAX_STRING_LENGTH]; + double number; + bool boolean; + char function_ref[MAX_SYMBOL_LENGTH]; + struct { + char function_name[MAX_SYMBOL_LENGTH]; + struct node *args[MAX_ARGS]; + int arg_count; + } call; + } data; +} node_t; + +// Lexer state +typedef struct { + const char *input; + int pos; + int line; + int column; + char current_char; +} lexer_t; + +// Configuration storage +typedef struct key_mapping { + char *key_combo; + char *function_name; + struct key_mapping *next; +} key_mapping_t; + +typedef struct config_var { + char *name; + node_t *value; + struct config_var *next; +} config_var_t; + +typedef struct { + key_mapping_t *key_mappings; + config_var_t *variables; +} config_t; + +void advance_char(lexer_t *lexer); +void skip_whitespace(lexer_t *lexer); +void skip_comment(lexer_t *lexer); +node_t *create_node(node_type_t type); +node_t *create_symbol_node(const char *symbol); +node_t *create_string_node(const char *string); +node_t *create_number_node(double number); +node_t *create_boolean_node(bool value); +node_t *create_function_ref_node(const char *function_name); +node_t *create_function_call_node(const char *function_name); + +void add_arg_to_call(node_t *call, node_t *args); + +#endif diff --git a/include/define.h b/include/define.h new file mode 100644 index 0000000..1aa64fa --- /dev/null +++ b/include/define.h @@ -0,0 +1,9 @@ +#ifndef BLISP_DEFINE_H_ +#define BLISP_DEFINE_H_ + +#define MAX_TOKEN_LENGTH 256 +#define MAX_SYMBOL_LENGTH 128 +#define MAX_STRING_LENGTH 512 +#define MAX_ARGS 16 + +#endif diff --git a/include/lexer.h b/include/lexer.h new file mode 100644 index 0000000..081a23b --- /dev/null +++ b/include/lexer.h @@ -0,0 +1,8 @@ +#ifndef LEXER_H_ +#define LEXER_H_ + +#include "data.h" + +token_t next_token(lexer_t *lexer); + +#endif diff --git a/include/parser.h b/include/parser.h new file mode 100644 index 0000000..6029a10 --- /dev/null +++ b/include/parser.h @@ -0,0 +1,11 @@ +#ifndef PARSER_H_ +#define PARSER_H_ + +#include "data.h" +#include "lexer.h" + +node_t *parse_atom(lexer_t *lexer, token_t *token); +node_t *parse_function_call(lexer_t *lexer, const char *function_name); +node_t *parse_statement(lexer_t *lexer); + +#endif diff --git a/init.bl b/init.bl new file mode 100644 index 0000000..741a275 --- /dev/null +++ b/init.bl @@ -0,0 +1,12 @@ +// Configuration file + +,map-key("CTRL-s" %editorSave) +// ,map-key("CTRL-q" %editorQuit) +,map-key("CTRL-a" %move-cursor-beg-line) +,map-key("CTRL-z" %move-cursor-end-line) +,map-key("CTRL-f o" %open-file) +,map-key("CTRL-w s h" %window-split-horizontal) +,map-key("CTRL-w s v" %window-split-vertical) + +,define(theme "dark") +,define(auto-save true) diff --git a/main.c b/main.c new file mode 100644 index 0000000..acf8bdd --- /dev/null +++ b/main.c @@ -0,0 +1,38 @@ + +#include "src/data.h" +#include "src/config_tools.h" + +int main() { + // Parse the configuration + config_t *config = config_create(); + char *config_file = "init.bl"; + if (!config) { + fprintf(stderr, "Error: Failed to create config structure\n"); + return 1; + } + + if (config_parse_file(config, config_file) != 0) { + fprintf(stderr, "Error: Failed to parse config file\n"); + config_destroy(config); + return 1; + } + + printf("Successfully parsed Lisp config file: %s\n\n", config_file); + + // Print all entries + config_print_all(config); + printf("\n"); + + // Demonstrate usage + printf("Example usage:\n"); + printf("CTRL-s maps to: %s\n", config_get_key_mapping(config, "CTRL-s")); + printf("CTRL-f o maps to: %s\n", config_get_key_mapping(config, "CTRL-f o")); + printf("Window width: %d\n", config_get_int(config, "window-width", 1024)); + printf("Theme: %s\n", config_get_string(config, "theme", "light")); + printf("Auto-save: %s\n", + config_get_bool(config, "auto-save", false) ? "enabled" : "disabled"); + printf("Font size: %d\n", config_get_int(config, "font-size", 12)); + + config_destroy(config); + return 0; +} diff --git a/src/config_tools.c b/src/config_tools.c new file mode 100644 index 0000000..1098633 --- /dev/null +++ b/src/config_tools.c @@ -0,0 +1,282 @@ +#include "../include/config_tools.h" +#include "../include/data.h" +#include + +config_t *config_create(void) { + /* + * @brief Initialize config structure + */ + config_t *config = malloc(sizeof(config_t)); + if (config) { + config->key_mappings = NULL; + config->variables = NULL; + } + return config; +} + +void free_node(node_t *node) { + int i; + if (!node) + return; + + if (node->type == NODE_FUNCTION_CALL) { + for (i = 0; i < node->data.call.arg_count; i++) { + free_node(node->data.call.args[i]); + } + // free(node->data.call.args); + } + + free(node); +} + +void config_destroy(config_t *config) { + if (!config) + return; + + key_mapping_t *mapping = config->key_mappings; + while (mapping) { + key_mapping_t *next = mapping->next; + free(mapping->key_combo); + free(mapping->function_name); + free(mapping); + mapping = next; + } + + // Free variables + config_var_t *var = config->variables; + while (var) { + config_var_t *next = var->next; + free(var->name); + free_node(var->value); + free(var); + var = next; + } + + free(config); +} + +// Built-in function handlers +int handle_map_key(config_t *config, node_t **args, int arg_count) { + if (arg_count != 2 || args[0]->type != NODE_STRING || + args[1]->type != NODE_FUNCTION_REF) { + fprintf(stderr, "Error: map-key requires (key_combo, function_ref)\n"); + return -1; + } + + key_mapping_t *mapping = malloc(sizeof(key_mapping_t)); + if (!mapping) + return -1; + + mapping->key_combo = strdup(args[0]->data.string); + mapping->function_name = strdup(args[1]->data.function_ref); + mapping->next = config->key_mappings; + config->key_mappings = mapping; + + return 0; +} + +int handle_define(config_t *config, node_t **args, int arg_count) { + if (arg_count != 2 || args[0]->type != NODE_SYMBOL) { + fprintf(stderr, "Error: define requires (variable_name, value)\n"); + return -1; + } + + config_var_t *var = malloc(sizeof(config_var_t)); + if (!var) + return -1; + + var->name = strdup(args[0]->data.symbol); + var->value = args[1]; // Transfer ownership + var->next = config->variables; + config->variables = var; + + return 0; +} + +int execute_function_call(config_t *config, node_t *call) { + if (call->type != NODE_FUNCTION_CALL) + return -1; + + // Look for built-in function + for (int i = 0; builtin_functions[i].name; i++) { + if (strcmp(call->data.call.function_name, builtin_functions[i].name) == 0) { + return builtin_functions[i].handler(config, call->data.call.args, + call->data.call.arg_count); + } + } + + fprintf(stderr, "Error: Unknown function '%s'\n", + call->data.call.function_name); + return -1; +} + +int config_parse_string(config_t *config, const char *input) { + lexer_t lexer = {0}; + lexer.input = input; + lexer.pos = 0; + lexer.line = 1; + lexer.column = 1; + lexer.current_char = input[0]; + + while (lexer.current_char != '\0') { + printf("%c", lexer.current_char); + skip_whitespace(&lexer); + skip_comment(&lexer); + skip_whitespace(&lexer); + + if (lexer.current_char == '\0') + break; + if (lexer.current_char == '\n') { + advance_char(&lexer); + continue; + } + + node_t *stmt = parse_statement(&lexer); + if (stmt) { + if (execute_function_call(config, stmt) != 0) { + fprintf(stderr, "Error executing statement at line %d\n", lexer.line); + } + if (stmt->type != NODE_FUNCTION_CALL || + strcmp(stmt->data.call.function_name, "define") != 0) { + free_node(stmt); // Don't free if ownership was transferred to define + } + } + + // Skip to next line + while (lexer.current_char != '\n' && lexer.current_char != '\0') { + advance_char(&lexer); + } + } + + return 0; +} + +int config_parse_file(config_t *config, const char *filename) { + FILE *file = fopen(filename, "r"); + long file_size = 0; + char *content = NULL; + int result = 0; + if (!file) { + perror("Error opening config file"); + return -1; + } + + fseek(file, 0, SEEK_END); + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + content = (char *)malloc(file_size + 1); + if (!content) { + fclose(file); + return -1; + } + + fread(content, 1, file_size, file); + content[file_size] = '\0'; + fclose(file); + + result = config_parse_string(config, content); + free(content); + + return result; +} + +// Getter functions + +const char *config_get_key_mapping(config_t *config, const char *key_combo) { + key_mapping_t *mapping = config->key_mappings; + while (mapping) { + if (strcmp(mapping->key_combo, key_combo) == 0) { + return mapping->function_name; + } + mapping = mapping->next; + } + return NULL; +} + +node_t *find_variable(config_t *config, const char *name) { + config_var_t *var = config->variables; + while (var) { + if (strcmp(var->name, name) == 0) { + return var->value; + } + var = var->next; + } + return NULL; +} + +const char *config_get_string(config_t *config, const char *path, + const char *default_value) { + node_t *node = find_variable(config, path); + if (node && node->type == NODE_STRING) { + return node->data.string; + } + return default_value; +} + +int config_get_int(config_t *config, const char *path, int default_value) { + node_t *node = find_variable(config, path); + if (node && node->type == NODE_NUMBER) { + return (int)node->data.number; + } + return default_value; +} + +double config_get_double(config_t *config, const char *path, + double default_value) { + node_t *node = find_variable(config, path); + if (node && node->type == NODE_NUMBER) { + return node->data.number; + } + return default_value; +} + +bool config_get_bool(config_t *config, const char *path, bool default_value) { + node_t *node = find_variable(config, path); + if (node && node->type == NODE_BOOLEAN) { + return node->data.boolean; + } + return default_value; +} + +void config_print_all(config_t *config) { + printf("Key Mappings:\n"); + printf("%-20s -> %s\n", "Key Combination", "Function"); + printf("%-20s %s\n", "---------------", "--------"); + + key_mapping_t *mapping = config->key_mappings; + while (mapping) { + printf("%-20s -> %s\n", mapping->key_combo, mapping->function_name); + mapping = mapping->next; + } + + printf("\nVariables:\n"); + printf("%-20s = %s\n", "Name", "Value"); + printf("%-20s %s\n", "----", "-----"); + + config_var_t *var = config->variables; + while (var) { + printf("%-20s = ", var->name); + + switch (var->value->type) { + case NODE_STRING: + printf("\"%s\"", var->value->data.string); + break; + case NODE_NUMBER: + printf("%g", var->value->data.number); + break; + case NODE_BOOLEAN: + printf("%s", var->value->data.boolean ? "true" : "false"); + break; + case NODE_SYMBOL: + printf("%s", var->value->data.symbol); + break; + default: + printf("[unknown type]"); + break; + } + printf("\n"); + + var = var->next; + } +} diff --git a/src/data.c b/src/data.c new file mode 100644 index 0000000..3e32e3a --- /dev/null +++ b/src/data.c @@ -0,0 +1,100 @@ +#include "../include/data.h" +#include "../include/define.h" +#include + +void advance_char(lexer_t *lexer) { + if (lexer->current_char == '\n') { + lexer->line++; + lexer->column = 1; + } else { + lexer->column++; + } + lexer->pos++; + lexer->current_char = lexer->input[lexer->pos]; +} + +void skip_whitespace(lexer_t *lexer) { + while (isspace(lexer->current_char) && lexer->current_char != '\n') { + advance_char(lexer); + } +} + +void skip_comment(lexer_t *lexer) { + if (lexer->current_char == '/' && lexer->input[lexer->pos + 1] == '/') { + while (lexer->current_char != '\n' && lexer->current_char != '\0') { + advance_char(lexer); + } + } +} + +// Node creation functions +node_t *create_node(node_type_t type) { + node_t *node = malloc(sizeof(node_t)); + if (!node) + return NULL; + + memset(node, 0, sizeof(node_t)); + node->type = type; + return node; +} + +node_t *create_symbol_node(const char *symbol) { + node_t *node = create_node(NODE_SYMBOL); + if (node) { + strncpy(node->data.symbol, symbol, MAX_SYMBOL_LENGTH - 1); + node->data.symbol[MAX_SYMBOL_LENGTH - 1] = '\0'; + } + return node; +} + +node_t *create_string_node(const char *string) { + node_t *node = create_node(NODE_STRING); + if (node) { + strncpy(node->data.string, string, MAX_STRING_LENGTH - 1); + node->data.string[MAX_STRING_LENGTH - 1] = '\0'; + } + return node; +} + +node_t *create_number_node(double number) { + node_t *node = create_node(NODE_NUMBER); + if (node) { + node->data.number = number; + } + return node; +} + +node_t *create_boolean_node(bool value) { + node_t *node = create_node(NODE_BOOLEAN); + if (node) { + node->data.boolean = value; + } + return node; +} + +node_t *create_function_ref_node(const char *function_name) { + node_t *node = create_node(NODE_FUNCTION_REF); + if (node) { + strncpy(node->data.function_ref, function_name, MAX_SYMBOL_LENGTH - 1); + node->data.function_ref[MAX_SYMBOL_LENGTH - 1] = '\0'; + } + return node; +} + +node_t *create_function_call_node(const char *function_name) { + node_t *node = create_node(NODE_FUNCTION_CALL); + if (node) { + strncpy(node->data.call.function_name, function_name, + MAX_SYMBOL_LENGTH - 1); + node->data.call.function_name[MAX_SYMBOL_LENGTH - 1] = '\0'; + node->data.call.arg_count = 0; + } + return node; +} + +void add_arg_to_call(node_t *call, node_t *arg) { + if (call->type == NODE_FUNCTION_CALL && + call->data.call.arg_count < MAX_ARGS) { + call->data.call.args[call->data.call.arg_count++] = arg; + } +} diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..2783676 --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,140 @@ +#include "../include/lexer.h" +#include + +token_t next_token(lexer_t *lexer) { + token_t token = {0}; + token.line = lexer->line; + token.column = lexer->column; + + skip_whitespace(lexer); + skip_comment(lexer); + skip_whitespace(lexer); + + if (lexer->current_char == '\0') { + token.type = TOKEN_EOF; + return token; + } + + // Newline (statement separator) + if (lexer->current_char == '\n') { + token.type = TOKEN_NEWLINE; + advance_char(lexer); + return token; + } + + // Comma (function call indicator) + if (lexer->current_char == ',') { + token.type = TOKEN_COMMA; + token.value[0] = ','; + token.value[1] = '\0'; + advance_char(lexer); + return token; + } + + // Percent (function reference indicator) + if (lexer->current_char == '%') { + token.type = TOKEN_PERCENT; + token.value[0] = '%'; + token.value[1] = '\0'; + advance_char(lexer); + return token; + } + + // Parentheses + if (lexer->current_char == '(') { + token.type = TOKEN_LPAREN; + token.value[0] = '('; + token.value[1] = '\0'; + advance_char(lexer); + return token; + } + + if (lexer->current_char == ')') { + token.type = TOKEN_RPAREN; + token.value[0] = ')'; + token.value[1] = '\0'; + advance_char(lexer); + return token; + } + + // Strings + if (lexer->current_char == '"') { + token.type = TOKEN_STRING; + advance_char(lexer); // Skip opening quote + + int i = 0; + while (lexer->current_char != '"' && lexer->current_char != '\0' && + i < MAX_TOKEN_LENGTH - 1) { + if (lexer->current_char == '\\' && lexer->input[lexer->pos + 1] != '\0') { + advance_char(lexer); + switch (lexer->current_char) { + case 'n': + token.value[i++] = '\n'; + break; + case 't': + token.value[i++] = '\t'; + break; + case 'r': + token.value[i++] = '\r'; + break; + case '\\': + token.value[i++] = '\\'; + break; + case '"': + token.value[i++] = '"'; + break; + default: + token.value[i++] = lexer->current_char; + break; + } + } else { + token.value[i++] = lexer->current_char; + } + advance_char(lexer); + } + token.value[i] = '\0'; + + if (lexer->current_char == '"') { + advance_char(lexer); // Skip closing quote + } + return token; + } + + // Numbers, symbols, and booleans + if (isalnum(lexer->current_char) || lexer->current_char == '-' || + lexer->current_char == '+' || lexer->current_char == '.' || + lexer->current_char == '_') { + + int i = 0; + while ( + (isalnum(lexer->current_char) || strchr("-+._", lexer->current_char)) && + i < MAX_TOKEN_LENGTH - 1) { + token.value[i++] = lexer->current_char; + advance_char(lexer); + } + token.value[i] = '\0'; + + // Check for booleans + if (strcmp(token.value, "true") == 0 || strcmp(token.value, "false") == 0) { + token.type = TOKEN_BOOLEAN; + } else { + // Check for numbers + char *endptr; + strtod(token.value, &endptr); + if (*endptr == '\0' && strlen(token.value) > 0 && + (isdigit(token.value[0]) || token.value[0] == '-' || + token.value[0] == '+')) { + token.type = TOKEN_NUMBER; + } else { + token.type = TOKEN_SYMBOL; + } + } + return token; + } + + // Error case + token.type = TOKEN_ERROR; + snprintf(token.value, MAX_TOKEN_LENGTH, "Unexpected character '%c'", + lexer->current_char); + return token; +} diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..ebf9557 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,85 @@ +#include "../include/parser.h" + +node_t *parse_atom(lexer_t *lexer, token_t *token) { + switch (token->type) { + case TOKEN_SYMBOL: + return create_symbol_node(token->value); + + case TOKEN_STRING: + return create_string_node(token->value); + + case TOKEN_NUMBER: + return create_number_node(atof(token->value)); + + case TOKEN_BOOLEAN: + return create_boolean_node(strcmp(token->value, "true") == 0); + + case TOKEN_PERCENT: { + // Parse function reference: %function-name + token_t next = next_token(lexer); + if (next.type == TOKEN_SYMBOL) { + return create_function_ref_node(next.value); + } + return NULL; + } + + default: + return NULL; + } +} + +node_t *parse_function_call(lexer_t *lexer, const char *function_name) { + node_t *call = create_function_call_node(function_name); + if (!call) + return NULL; + + token_t token = next_token(lexer); + if (token.type != TOKEN_LPAREN) { + // Function call without parentheses - no arguments + printf("no paren\n"); + return call; + } + + // Parse arguments + token = next_token(lexer); + while (token.type != TOKEN_RPAREN && token.type != TOKEN_EOF) { + node_t *arg = NULL; + + if (token.type == TOKEN_PERCENT) { + // Function reference argument + token_t func_token = next_token(lexer); + if (func_token.type == TOKEN_SYMBOL) { + arg = create_function_ref_node(func_token.value); + } + } else { + // Regular argument + arg = parse_atom(lexer, &token); + } + + if (arg) { + add_arg_to_call(call, arg); + } + + token = next_token(lexer); + } + + return call; +} + +node_t *parse_statement(lexer_t *lexer) { + skip_whitespace(lexer); + skip_comment(lexer); + skip_whitespace(lexer); + + token_t token = next_token(lexer); + + if (token.type == TOKEN_COMMA) { + // Function call: ,function-name(args) + token_t func_token = next_token(lexer); + if (func_token.type == TOKEN_SYMBOL) { + return parse_function_call(lexer, func_token.value); + } + } + + return NULL; +}