// This software disclaims copyright. Do what you want with it. Be gay, do // crime. Originally written by Alexis Lockwood in 2021. Ⓐ // --- DEPENDENCIES ------------------------------------------------------------ // Supporting modules #include "ls.h" #include "ls_internal.h" #include "ls_lex.h" #include "ls_minify_identgen.h" // External dependencies #include #include #include #include // Standard headers #include #include #include #include #include #include #include #include #include #include // --- PRIVATE MACROS ---------------------------------------------------------- // --- PRIVATE DATATYPES ------------------------------------------------------- // --- PRIVATE CONSTANTS ------------------------------------------------------- // --- PRIVATE FUNCTION PROTOTYPES --------------------------------------------- static int _fetcher(void * arg, uint16_t loc); static void _usage(char const * argv0, bool short_text); static void _minify(FILE * f_out); static void _min_word_or_str_label(ls_token_t tok); static void _min_number_or_num_label(ls_token_t tok); static void _min_float(ls_token_t tok); static ls_token_t _min_keyword(ls_token_t tok); static void _min_string(ls_token_t tok); static void _min_operator(ls_token_t tok); static void _min_comma(ls_token_t tok); static void _min_semicolon(ls_token_t tok); static void _min_newline(ls_token_t tok); // --- PUBLIC VARIABLES -------------------------------------------------------- // --- PRIVATE VARIABLES ------------------------------------------------------- static uint8_t * g_code; ///< Code being minified static size_t g_code_len; ///< Length of code static FILE * g_f_out; ///< Output stream static ls_t g_ls; ///< Littlescript instance static ls_token_t g_last_tok; ///< Last token processed static bool g_eol_at_end; ///< Whether an EOL was emitted at end static bool g_have_ident_table; ///< Ident table was found /// Whether a space should be added before words/numbers static bool g_add_space; static bool opt_keep_rems; static bool opt_min_idents; static bool opt_emit_ident_table; static bool opt_un_minify; // --- PUBLIC FUNCTIONS -------------------------------------------------------- int main(int argc, char ** argv) { int opt; while ((opt = getopt(argc, argv, "hiIru")) != -1) { switch (opt) { default: _usage(argv[0], false); exit(opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE); break; case 'I': opt_emit_ident_table = true; // fall through case 'i': opt_min_idents = true; break; case 'r': opt_keep_rems = true; break; case 'u': opt_un_minify = true; break; } } FILE * f = NULL; if (optind >= argc) f = stdin; else if (!strcmp(argv[optind], "-")) f = stdin; else f = explain_fopen_or_die(argv[optind], "r"); size_t sz = 512; g_code = explain_malloc_or_die(sz); g_code_len = 0; while (!feof(f)) { explain_ferror_or_die(f); size_t read_sz = sz - g_code_len; size_t n = fread(&g_code[g_code_len], 1, sz - g_code_len, f); g_code_len += n; if (read_sz == n) { sz *= 2; g_code = explain_realloc_or_die(g_code, sz); } } if (f != stdin) fclose(f); if (optind + 1 < argc) { if (!strcmp(argv[optind + 1], "-")) f = stdout; else f = explain_fopen_or_die(argv[optind + 1], "w"); } else f = stdout; if (opt_un_minify) g_have_ident_table = ls_minify_load_mit(g_code, &g_code_len); _minify(f); if (opt_emit_ident_table) { if (!g_eol_at_end) fprintf(f, "\n"); ls_minify_emit_mit(f); } free(g_code); if (f != stdout) fclose(f); return 0; } // --- PRIVATE FUNCTION DEFINITIONS -------------------------------------------- static int _fetcher(void * arg, uint16_t loc) { (void) arg; if ((size_t) loc >= g_code_len) return -LS_NO_MORE_PROGRAM; else return (ls_uchar) g_code[(size_t) loc]; } static void _usage(char const * argv0, bool short_text) { fprintf(stderr, "usage: %s [OPTION...] [INPUT [OUTPUT]]\n", argv0); if (short_text) return; fprintf(stderr, "Minify a Littlescript file\n"); fprintf(stderr, "\n"); fprintf(stderr, " -h display this help text\n"); fprintf(stderr, "\n"); fprintf(stderr, " -i minify identifiers, making the code much\n"); fprintf(stderr, " less readable after un-minification\n"); fprintf(stderr, " -I -i, but also emit an idents table to allow\n"); fprintf(stderr, " full un-minification\n"); fprintf(stderr, " -r keep REM comments\n"); fprintf(stderr, " -u un-minify\n"); } static void _minify(FILE * f_out) { ls_value_t pool[100]; // for labels ls_init(&g_ls, pool, sizeof pool / sizeof pool[0]); g_ls.fetcher = _fetcher; g_ls.fetcher_arg = NULL; if (setjmp(g_ls._error_jmp_buf)) { uint16_t line = 0, col = 0; ls_translate_pc(&g_ls, g_ls._pc, &line, &col); fprintf(stderr, "error %d at %u:%u", (int) g_ls._error, line, col); exit(EXIT_FAILURE); } g_f_out = f_out; g_last_tok = LS_TOK_NONE; bool done = false; while (!done) { ls_token_t tok = ls_lex(&g_ls); g_add_space = g_last_tok == LS_TOK_NUMBER || g_last_tok == LS_TOK_WORD || (LS_TOK_KEYWORD(g_last_tok) && opt_un_minify) || (LS_TOK_OPER(g_last_tok) && opt_un_minify) || (g_last_tok == LS_TOK_COMMA && opt_un_minify) || tok == LS_KW_ELSE; ls_token_t tok_for_last_tok = tok; switch (tok) { case LS_TOK_NUMBER: case LS_TOK_NUM_LABEL: _min_number_or_num_label(tok); break; case LS_TOK_WORD: case LS_TOK_STR_LABEL: _min_word_or_str_label(tok); break; case LS_TOK_FLOAT: _min_float(tok); break; case LS_TOK_STRING: _min_string(tok); break; case LS_TOK_COMMA: _min_comma(tok); break; case LS_TOK_SEMICOLON: _min_semicolon(tok); break; case LS_TOK_NEWLINE: g_eol_at_end = true; _min_newline(tok); break; case LS_TOK_INVALID: ls_throw_err(&g_ls, LS_SYNTAX_ERROR); break; default: if (LS_TOK_KEYWORD(tok)) tok_for_last_tok = _min_keyword(tok); else if (LS_TOK_OPER(tok)) _min_operator(tok); else ls_throw_err(&g_ls, LS_INTERNAL_ERROR); break; case LS_TOK_NONE: done = true; g_eol_at_end = (g_last_tok == LS_TOK_NEWLINE); break; } g_last_tok = tok_for_last_tok; } } static void _min_word_or_str_label(ls_token_t tok) { if (g_add_space) fprintf(g_f_out, " "); assert(g_ls._token.word[LS_IDENT_OR_KW_LEN] == 0); g_ls._token.word[LS_IDENT_OR_KW_LEN] = 0; char const * ident; if (opt_min_idents || g_have_ident_table) ident = ls_minify_identgen(g_ls._token.word); else ident = g_ls._token.word; fprintf(g_f_out, "%s%s", ident, tok == LS_TOK_WORD ? "" : opt_un_minify ? ": " : ":"); } static void _min_number_or_num_label(ls_token_t tok) { if (g_add_space) fprintf(g_f_out, " "); fprintf(g_f_out, "%"PRId32"%s", g_ls._token.number, tok == LS_TOK_NUMBER ? "" : opt_un_minify ? ": " : ":"); } static void _min_float(ls_token_t tok) { (void) tok; ls_float_t f = g_ls._token.float_; ls_float_t g; char buf[32]; int prec; for (prec = 25; prec > 0; prec -= 1) { snprintf(buf, sizeof(buf), "%.*g", prec, f); g = strtod(buf, NULL); if (g != f) break; } if (g_add_space) fprintf(g_f_out, " "); fprintf(g_f_out, "%.*g", prec + 1, f); } static ls_token_t _min_keyword(ls_token_t tok) { if (tok == LS_KW_REM) { if (opt_un_minify) { if (g_add_space) fprintf(g_f_out, " "); fprintf(g_f_out, "REM "); } else if (opt_keep_rems) { uint8_t c = LS_KW_REM; fwrite(&c, 1, 1, g_f_out); } ls_addr_t pc_start; ls_addr_t pc_end; for (pc_start = g_ls._pc;; pc_start++) { if (pc_start >= g_code_len) break; if (!(g_code[pc_start] == ' ' || g_code[pc_start] == '\t')) break; } for (pc_end = pc_start;; pc_end++) { if (pc_end >= g_code_len) break; if (g_code[pc_end] == '\n') break; } g_ls._pc = (ls_addr_t)(pc_end + 1); if (opt_un_minify || opt_keep_rems) { fwrite(&g_code[pc_start], (size_t)(pc_end - pc_start), 1, g_f_out); fprintf(g_f_out, "\n"); // We just emitted a \n, make sure the // next statement reflects that return LS_TOK_NEWLINE; } else { // Skipped entirely... return (uint8_t) g_last_tok; } } else if (!opt_un_minify) { uint8_t c = (uint8_t) tok; fwrite(&c, 1, 1, g_f_out); return tok; } else { if (g_add_space) fprintf(g_f_out, " "); fprintf(g_f_out, "%s", ls_kwmap[tok - LS_KW_OFFSET].name); return tok; } } static void _min_string(ls_token_t tok) { (void) tok; if (opt_un_minify) fprintf(g_f_out, " "); fprintf(g_f_out, "\""); for (ls_addr_t i = g_ls._token.string[0]; i <= g_ls._token.string[1]; i++) { fprintf(g_f_out, "%c", g_code[i]); } fprintf(g_f_out, "\""); } static void _min_operator(ls_token_t tok) { static const unsigned char ops[][3] = { [LS_OP_LEQ] = "<=", [LS_OP_GEQ] = ">=", [LS_OP_NEQ] = "<>", [LS_OP_LPAREN] = "(", [LS_OP_RPAREN] = ")", [LS_OP_MOD] = "%", [LS_OP_MUL] = "*", [LS_OP_ADD] = "+", [LS_OP_SUB] = "-", [LS_OP_DIV] = "/", [LS_OP_POW] = "^", [LS_OP_LT] = "<", [LS_OP_EQ] = "=", [LS_OP_GT] = ">", }; assert(tok <= LS_OP_GT); if (opt_un_minify) fprintf(g_f_out, " "); fprintf(g_f_out, "%s", ops[tok]); } static void _min_comma(ls_token_t tok) { (void) tok; fprintf(g_f_out, ","); } static void _min_semicolon(ls_token_t tok) { (void) tok; fprintf(g_f_out, ";"); } static void _min_newline(ls_token_t tok) { (void) tok; if (g_last_tok != LS_TOK_NEWLINE && g_last_tok != LS_TOK_STR_LABEL && g_last_tok != LS_TOK_NUM_LABEL ) fprintf(g_f_out, "\n"); }