Skip to content

Commit

Permalink
Merge pull request #186 from KennyOliver/issue-185
Browse files Browse the repository at this point in the history
Issue 185: Implement a Minifier (`--minify`)
  • Loading branch information
KennyOliver authored Jan 8, 2025
2 parents f642e04 + 6400877 commit b376292
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
src/flavor
src/flavor.dSYM/
obj
*.min.flv

# VS Code Extension
vscode-extension/node_modules/
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,10 @@ This will print detailed information about the tokenization and parsing process.
## 🚀 Execution Flags & Behaviors <a id="execution-flags--behaviors"></a>

```bash
$ flavor recipe.flv # Default execution
$ flavor recipe.flv --debug # Debug mode
$ flavor --about # About FlavorLang
$ flavor recipe.flv # Default execution
$ flavor recipe.flv --debug # Debug mode
$ flavor recipe.flv --minify # Minify a script
$ flavor --about # About FlavorLang
```

> [!Note]
Expand Down
1 change: 0 additions & 1 deletion src/debug/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ extern bool debug_flag;

typedef enum { GENERAL, LEXER, PARSER, INTERPRETER } DebugMode;

void parse_cli_args(int argc, char *argv[]);
void debug_print_basic(const char *message, ...);
void debug_print_lex(const char *message, ...);
void debug_print_par(const char *message, ...);
Expand Down
242 changes: 214 additions & 28 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,88 @@
#include <stdlib.h>
#include <string.h>

void parse_cli_args(int argc, char *argv[]) {
// Structure to hold command-line options
typedef struct {
bool minify;
char *filename;
} Options;

// Function prototypes
void parse_cli_args(int argc, char *argv[], Options *options);
void print_usage(const char *prog_name);
void print_logo_from_file(const char *filename);
char *generate_minified_filename(const char *input_filename);
void minify_tokens(Token *tokens, const char *output_file);
void print_about(void);

// Print usage instructions
void print_usage(const char *prog_name) {
fprintf(stderr, "Usage: `%s <file.flv> [--debug | --minify]`\n", prog_name);
fprintf(stderr, "Note: Combining --debug and --minify is invalid.\n");
}

// Parse command-line arguments
void parse_cli_args(int argc, char *argv[], Options *options) {
if (argc < 2) {
fprintf(stderr, "Error: No `.flv` source file provided.\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}

debug_flag = false;
options->minify = false;
options->filename = NULL;

for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--debug") == 0) {
if (options->minify) {
fprintf(stderr,
"Error: Cannot use --debug and --minify together.\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
debug_flag = true;
} else if (strcmp(argv[i], "--minify") == 0) {
if (debug_flag) {
fprintf(stderr,
"Error: Cannot use --debug and --minify together.\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
options->minify = true;
} else if (argv[i][0] == '-') {
fprintf(stderr, "Error: Unknown option '%s'.\n", argv[i]);
print_usage(argv[0]);
exit(EXIT_FAILURE);
} else {
if (options->filename != NULL) {
fprintf(stderr, "Error: Multiple input files provided.\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
options->filename = argv[i];
}
}

if (options->filename == NULL) {
fprintf(stderr, "Error: No `.flv` source file provided.\n");
print_usage(argv[0]);
exit(EXIT_FAILURE);
}

// Validate file extension
const char *dot = strrchr(options->filename, '.');
if (!dot || strcmp(dot, ".flv") != 0) {
fprintf(stderr, "Error: Input file must have a `.flv` extension.\n");
exit(EXIT_FAILURE);
}
}

// Print logo from a file
void print_logo_from_file(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Error opening file");
perror("Error opening logo file");
return;
}

Expand All @@ -30,6 +100,94 @@ void print_logo_from_file(const char *filename) {
fclose(file);
}

// Generate minified filename
char *generate_minified_filename(const char *input_filename) {
const char *dot = strrchr(input_filename, '.');
size_t min_filename_len;

if (!dot || dot == input_filename) {
// No extension found, append .min.flv
min_filename_len = strlen(input_filename) + strlen(".min.flv") + 1;
} else {
// Calculate position to insert ".min"
size_t basename_len = dot - input_filename;
min_filename_len = basename_len + strlen(".min") + strlen(dot) + 1;
}

char *min_filename = malloc(min_filename_len);
if (!min_filename) {
perror("Memory allocation failed");
exit(EXIT_FAILURE);
}

if (!dot || dot == input_filename) {
snprintf(min_filename, min_filename_len, "%s.min.flv", input_filename);
} else {
size_t basename_len = dot - input_filename;
snprintf(min_filename, min_filename_len, "%.*s.min%s",
(int)basename_len, input_filename, dot);
}

return min_filename;
}

// Minify tokens
void minify_tokens(Token *tokens, const char *output_file) {
FILE *output = fopen(output_file, "w");
if (!output) {
perror("Error opening output file for minification");
exit(EXIT_FAILURE);
}

Token *current = tokens;
Token *next = tokens + 1;

while (current->type != TOKEN_EOF) {
switch (current->type) {
case TOKEN_STRING:
fputc('"', output); // Opening quote
fputs(current->lexeme, output);
fputc('"', output); // Closing quote
break;

default:
fputs(current->lexeme, output);
break;
}

// Add necessary spacing between tokens
if (next->type != TOKEN_EOF) {
bool add_space = false;

if (current->type == TOKEN_KEYWORD &&
next->type == TOKEN_IDENTIFIER) {
add_space = true;
} else if ((current->type == TOKEN_IDENTIFIER ||
current->type == TOKEN_STRING ||
current->type == TOKEN_INTEGER ||
current->type == TOKEN_FLOAT ||
current->type == TOKEN_BOOLEAN) &&
(next->type == TOKEN_IDENTIFIER ||
next->type == TOKEN_STRING ||
next->type == TOKEN_INTEGER ||
next->type == TOKEN_FLOAT ||
next->type == TOKEN_BOOLEAN)) {
add_space = true;
}

if (add_space) {
fputc(' ', output);
}
}

current++;
next++;
}

fclose(output);
}

// Print about information
void print_about(void) {
printf("\n");
print_logo_from_file("../logo/logo.txt");
Expand All @@ -49,56 +207,84 @@ void print_about(void) {
}

int main(int argc, char **argv) {
// Check for `--about` flag
// Handle --about flag separately
if (argc == 2 && strcmp(argv[1], "--about") == 0) {
print_about();
return 0; // Exit after printing "about" info
}

// Validate input file arguments
if (argc < 2) {
fprintf(stderr, "Usage: `%s <file.flv>`\n", argv[0]);
fprintf(stderr, "Reason: no `.flv` source file was provided\n");
return 1;
} else if (argc > 3) {
fprintf(stderr, "Usage: `%s <file.flv>`\n", argv[0]);
fprintf(stderr, "Reason: FlavorLang currently only accepts one `.flv` "
"source file\n");
return 1;
return EXIT_SUCCESS;
}

parse_cli_args(argc, argv);

// Get filename
const char *filename = argv[1];
// Parse command-line arguments
Options options;
parse_cli_args(argc, argv, &options);

// Read source `.flv` file
char *source = read_file(filename);
char *source = read_file(options.filename);
if (!source) {
fprintf(stderr, "Error: Could not read file '%s'\n", options.filename);
return EXIT_FAILURE;
}

// Tokenize
Token *tokens = tokenize(source);
debug_print_tokens(tokens);
debug_print_basic("Lexing complete!\n\n");
if (!tokens) {
fprintf(stderr, "Error: Tokenization failed\n");
free(source);
return EXIT_FAILURE;
}

// If --minify flag is set, perform minification and exit
if (options.minify) {
char *minified_filename = generate_minified_filename(options.filename);
minify_tokens(tokens, minified_filename);
printf("Minified script written to '%s'\n", minified_filename);
free(minified_filename);

// Clean up memory
free(tokens);
free(source);
return EXIT_SUCCESS;
}

// If --debug flag is set, print debug information
if (debug_flag) {
debug_print_tokens(tokens);
debug_print_basic("Lexing complete!\n\n");
}

// Parse
ASTNode *ast = parse_program(tokens);
debug_print_basic("Parsing complete!\n\n");
print_ast(ast, 0);
debug_print_basic("Finished printing AST\n\n");
if (debug_flag) {
debug_print_basic("Parsing complete!\n\n");
print_ast(ast, 0);
debug_print_basic("Finished printing AST\n\n");
}

// Create environment
Environment env;
init_environment(&env);

// Interpret
interpret_program(ast, &env);
debug_print_basic("Execution complete!\n");
if (debug_flag) {
debug_print_basic("Execution complete!\n");
}

// Minify if flag is set (This check is redundant now since we already
// handled minify above)
/*
if (options.minify) {
char *minified_filename = generate_minified_filename(options.filename);
minify_tokens(tokens, minified_filename);
printf("Minified script written to '%s'\n", minified_filename);
free(minified_filename);
}
*/

// Clean up memory
free(tokens);
free(source);
free_environment(&env);
free_ast(ast);

return 0;
return EXIT_SUCCESS;
}
14 changes: 7 additions & 7 deletions src/tests/all.flv
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ serve(data);
# ==================================================
# 12
# ==================================================
const time = 20;
# const time = 20;

serve("Before error.");
# serve("Before error.");

if time > 15 {
burn("Too late!", "The food got burnt!");
}
# if time > 15 {
# burn("Too late!", "The food got burnt!");
# }

serve("After error?");
# serve("After error?");


# ==================================================
Expand Down Expand Up @@ -212,7 +212,7 @@ for i in 1..=10 {
}

# Using `burn()` to raise an error
burn("This is a fatal error with code:", 1001);
# burn("This is a fatal error with code:", 1001);


# ==================================================
Expand Down

0 comments on commit b376292

Please sign in to comment.