From 0a18b2cf2d77dd5465d1313af57d86500aa995ed Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 9 Sep 2026 12:47:06 +0200 Subject: [PATCH] feat: refactor syntax higlighting support in edit.elf/Text Editor, add highlighting for html, css, js --- kernel/src/Api/BuildNo.hpp | 2 +- programs/include/edit/syntax_ansi.hpp | 680 ------------------ programs/include/syntax/lang_c.hpp | 321 +++++++++ programs/include/syntax/lang_html.hpp | 475 +++++++++++++ programs/include/syntax/lang_lua.hpp | 160 +++++ programs/include/syntax/syntax.hpp | 299 ++++++++ programs/src/edit/main.cpp | 41 +- programs/src/texteditor/main.cpp | 2 +- programs/src/texteditor/syntax_highlight.hpp | 686 +------------------ 9 files changed, 1289 insertions(+), 1377 deletions(-) delete mode 100644 programs/include/edit/syntax_ansi.hpp create mode 100644 programs/include/syntax/lang_c.hpp create mode 100644 programs/include/syntax/lang_html.hpp create mode 100644 programs/include/syntax/lang_lua.hpp create mode 100644 programs/include/syntax/syntax.hpp diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 765ea72..600ad57 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 184 +#define MONTAUK_BUILD_NUMBER 185 diff --git a/programs/include/edit/syntax_ansi.hpp b/programs/include/edit/syntax_ansi.hpp deleted file mode 100644 index a249958..0000000 --- a/programs/include/edit/syntax_ansi.hpp +++ /dev/null @@ -1,680 +0,0 @@ -/* - * syntax_ansi.hpp - * ANSI terminal syntax highlighting for C, C++ and Lua - * Used by the MontaukOS CLI text editor - * Copyright (c) 2026 Daniel Hammer -*/ - -#pragma once - -#include - -// ============================================================================ -// Token types -// ============================================================================ - -enum SynLanguage : uint8_t { - SYN_LANG_NONE, - SYN_LANG_C, - SYN_LANG_CPP, - SYN_LANG_LUA, -}; - -enum SynToken : uint8_t { - SYN_NORMAL, - SYN_KEYWORD, - SYN_TYPE, - SYN_PREPROCESSOR, - SYN_STRING, - SYN_CHAR, - SYN_COMMENT, - SYN_NUMBER, - SYN_OPERATOR, -}; - -#define SYN_RAW_DELIM_MAX 16 - -struct SynState { - int in_block_comment; // 0 or 1 (bool stored as int for freestanding) - SynToken long_token; - int long_bracket_eqs; - bool in_raw_string; - char raw_delim[SYN_RAW_DELIM_MAX]; - int raw_delim_len; -}; - -// ============================================================================ -// Helpers -// ============================================================================ - -inline bool syn_is_alpha(char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -inline bool syn_is_alnum(char c) { - return syn_is_alpha(c) || (c >= '0' && c <= '9'); -} - -inline bool syn_is_digit(char c) { - return c >= '0' && c <= '9'; -} - -inline bool syn_is_hex(char c) { - return syn_is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -inline void syn_set_token(SynToken* out, int out_len, int idx, SynToken tok) { - if (out && idx >= 0 && idx < out_len) - out[idx] = tok; -} - -inline void syn_fill_tokens(SynToken* out, int out_len, int start, int end, SynToken tok) { - if (!out) return; - if (start < 0) start = 0; - if (end > out_len) end = out_len; - for (int i = start; i < end; i++) - out[i] = tok; -} - -inline bool syn_streq(const char* buf, int len, const char* kw) { - int i = 0; - while (i < len && kw[i]) { - if (buf[i] != kw[i]) return false; - i++; - } - return i == len && kw[i] == '\0'; -} - -// ============================================================================ -// Keyword / type classification -// ============================================================================ - -inline SynToken syn_classify_c_word(const char* buf, int len) { - static const char* keywords[] = { - "auto", "break", "case", "const", "continue", "default", "do", - "else", "enum", "extern", "for", "goto", "if", "inline", - "register", "restrict", "return", "sizeof", "static", "struct", - "switch", "typedef", "union", "volatile", "while", - "NULL", "true", "false", "nullptr", - }; - static const char* types[] = { - "void", "char", "short", "int", "long", "float", "double", - "signed", "unsigned", "bool", "_Bool", - "int8_t", "int16_t", "int32_t", "int64_t", - "uint8_t", "uint16_t", "uint32_t", "uint64_t", - "size_t", "ssize_t", "ptrdiff_t", "intptr_t", "uintptr_t", - "FILE", - }; - for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { - if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; - } - for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { - if (syn_streq(buf, len, types[i])) return SYN_TYPE; - } - return SYN_NORMAL; -} - -inline SynToken syn_classify_lua_word(const char* buf, int len) { - static const char* keywords[] = { - "and", "break", "do", "else", "elseif", "end", "false", - "for", "function", "goto", "if", "in", "local", "nil", - "not", "or", "repeat", "return", "then", "true", - "until", "while", - }; - static const char* builtins[] = { - "_ENV", "_G", "_VERSION", - "assert", "collectgarbage", "coroutine", "debug", "dofile", - "error", "getmetatable", "io", "ipairs", "load", "loadfile", - "math", "next", "os", "package", "pairs", "pcall", - "print", "rawequal", "rawget", "rawlen", "rawset", "require", - "select", "setmetatable", "string", "table", "tonumber", - "tostring", "type", "utf8", "warn", "xpcall", - }; - for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { - if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; - } - for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) { - if (syn_streq(buf, len, builtins[i])) return SYN_TYPE; - } - return SYN_NORMAL; -} - -inline SynToken syn_classify_cpp_word(const char* buf, int len) { - // C++ keywords (on top of the C set, which is checked first) - static const char* keywords[] = { - "alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", - "catch", "class", "compl", "concept", "consteval", "constexpr", - "constinit", "const_cast", "co_await", "co_return", "co_yield", - "decltype", "delete", "dynamic_cast", "explicit", "export", - "final", "friend", "mutable", "namespace", "new", "noexcept", - "not", "not_eq", "operator", "or", "or_eq", "override", - "private", "protected", "public", "reinterpret_cast", "requires", - "static_assert", "static_cast", "template", "this", "thread_local", - "throw", "try", "typeid", "typename", "using", "virtual", - "xor", "xor_eq", - }; - // C++ library / builtin types - static const char* types[] = { - "char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t", - "std", "string", "string_view", "vector", "array", "span", - "map", "set", "unordered_map", "unordered_set", "deque", "list", - "pair", "tuple", "optional", "variant", "function", - "unique_ptr", "shared_ptr", "weak_ptr", "initializer_list", - }; - - SynToken c = syn_classify_c_word(buf, len); - if (c != SYN_NORMAL) return c; - - for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { - if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; - } - for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { - if (syn_streq(buf, len, types[i])) return SYN_TYPE; - } - return SYN_NORMAL; -} - -inline SynToken syn_classify_word(SynLanguage lang, const char* buf, int len) { - switch (lang) { - case SYN_LANG_C: return syn_classify_c_word(buf, len); - case SYN_LANG_CPP: return syn_classify_cpp_word(buf, len); - case SYN_LANG_LUA: return syn_classify_lua_word(buf, len); - default: return SYN_NORMAL; - } -} - -inline SynState syn_make_state() { - SynState state = {}; - state.in_block_comment = 0; - state.long_token = SYN_NORMAL; - state.long_bracket_eqs = -1; - state.in_raw_string = false; - state.raw_delim_len = 0; - return state; -} - -// ============================================================================ -// Lua long bracket matching -// ============================================================================ - -inline bool syn_match_lua_long_bracket_open(const char* line, int len, int i, - int& eqs, int& span) { - if (i >= len || line[i] != '[') return false; - int j = i + 1; - while (j < len && line[j] == '=') j++; - if (j < len && line[j] == '[') { - eqs = j - i - 1; - span = j - i + 1; - return true; - } - return false; -} - -inline bool syn_match_lua_long_bracket_close(const char* line, int len, int i, - int eqs, int& span) { - if (i >= len || line[i] != ']') return false; - int j = i + 1; - for (int k = 0; k < eqs; k++) { - if (j >= len || line[j] != '=') return false; - j++; - } - if (j < len && line[j] == ']') { - span = j - i + 1; - return true; - } - return false; -} - -// ============================================================================ -// Number consumption -// ============================================================================ - -inline void syn_scan_digits(const char* line, int len, int& i, bool sep, bool hex) { - while (i < len) { - if (hex ? syn_is_hex(line[i]) : syn_is_digit(line[i])) { i++; continue; } - // C++14 digit separator: a quote wedged between two digits - if (sep && line[i] == '\'' && i + 1 < len && - (hex ? syn_is_hex(line[i + 1]) : syn_is_digit(line[i + 1]))) { - i += 2; - continue; - } - break; - } -} - -inline void syn_consume_number(const char* line, int len, int& i, - SynToken* out, int out_len, bool c_style_suffixes, - bool digit_sep = false) { - int start = i; - - if (line[i] == '0' && i + 1 < len && (line[i + 1] == 'x' || line[i + 1] == 'X')) { - i += 2; - syn_scan_digits(line, len, i, digit_sep, true); - if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { - i++; - syn_scan_digits(line, len, i, digit_sep, true); - } - if (i < len && (line[i] == 'p' || line[i] == 'P')) { - int exp = i + 1; - if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; - if (exp < len && syn_is_digit(line[exp])) { - i = exp; - syn_scan_digits(line, len, i, digit_sep, false); - } - } - } else { - if (line[i] == '.') i++; - syn_scan_digits(line, len, i, digit_sep, false); - if (i < len && line[i] == '.' && !(i + 1 < len && line[i + 1] == '.')) { - i++; - syn_scan_digits(line, len, i, digit_sep, false); - } - if (i < len && (line[i] == 'e' || line[i] == 'E')) { - int exp = i + 1; - if (exp < len && (line[exp] == '+' || line[exp] == '-')) exp++; - if (exp < len && syn_is_digit(line[exp])) { - i = exp; - syn_scan_digits(line, len, i, digit_sep, false); - } - } - } - - if (c_style_suffixes) { - while (i < len && (line[i] == 'u' || line[i] == 'U' || - line[i] == 'l' || line[i] == 'L' || - line[i] == 'f' || line[i] == 'F' || - line[i] == 'z' || line[i] == 'Z')) - i++; - } - - syn_fill_tokens(out, out_len, start, i, SYN_NUMBER); -} - -// ============================================================================ -// C highlighting -// ============================================================================ -// C / C++ raw string helpers -// ============================================================================ - -inline bool syn_is_raw_string_prefix(const char* buf, int len) { - return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") || - syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") || - syn_streq(buf, len, "u8R"); -} - -inline bool syn_is_string_prefix(const char* buf, int len) { - return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") || - syn_streq(buf, len, "U") || syn_streq(buf, len, "u8"); -} - -// Colors a raw-string body from `i` up to and including the closing )delim". -// If the terminator is not on this line, leaves state.in_raw_string set so the -// next line continues the literal. -inline void syn_consume_raw_body(const char* line, int len, int& i, - SynToken* out, int out_len, SynState& state) { - while (i < len) { - if (line[i] == ')') { - int j = i + 1; - int k = 0; - while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) { - j++; - k++; - } - if (k == state.raw_delim_len && j < len && line[j] == '"') { - syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING); - i = j + 1; - state.in_raw_string = false; - state.raw_delim_len = 0; - return; - } - } - syn_set_token(out, out_len, i, SYN_STRING); - i++; - } -} - -// Enters a raw string; `line[i]` must be the opening quote. -inline void syn_consume_raw_string(const char* line, int len, int& i, - SynToken* out, int out_len, SynState& state) { - syn_set_token(out, out_len, i, SYN_STRING); - i++; - state.raw_delim_len = 0; - while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) { - state.raw_delim[state.raw_delim_len++] = line[i]; - syn_set_token(out, out_len, i, SYN_STRING); - i++; - } - if (i < len && line[i] == '(') { - syn_set_token(out, out_len, i, SYN_STRING); - i++; - state.in_raw_string = true; - syn_consume_raw_body(line, len, i, out, out_len, state); - return; - } - // Malformed (or an over-long delimiter): treat the rest of the line as string - syn_fill_tokens(out, out_len, i, len, SYN_STRING); - i = len; -} - -// ============================================================================ -// C / C++ highlighting -// ============================================================================ - -inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, - SynState& state, SynLanguage lang = SYN_LANG_C) { - const bool cpp = (lang == SYN_LANG_CPP); - int i = 0; - - while (i < len) { - // ---- Raw string continuation ---- - if (state.in_raw_string) { - syn_consume_raw_body(line, len, i, out, out_len, state); - continue; - } - - // ---- Block comment continuation ---- - if (state.in_block_comment) { - while (i < len) { - if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { - syn_set_token(out, out_len, i, SYN_COMMENT); - syn_set_token(out, out_len, i + 1, SYN_COMMENT); - i += 2; - state.in_block_comment = false; - break; - } - syn_set_token(out, out_len, i, SYN_COMMENT); - i++; - } - continue; - } - - char c = line[i]; - - // ---- Line comment ---- - if (c == '/' && i + 1 < len && line[i + 1] == '/') { - syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); - break; - } - - // ---- Block comment start ---- - if (c == '/' && i + 1 < len && line[i + 1] == '*') { - state.in_block_comment = true; - syn_set_token(out, out_len, i, SYN_COMMENT); - syn_set_token(out, out_len, i + 1, SYN_COMMENT); - i += 2; - continue; - } - - // ---- Preprocessor directive ---- - if (c == '#') { - // Check that only whitespace precedes the # - bool is_pp = true; - for (int j = 0; j < i; j++) { - if (line[j] != ' ' && line[j] != '\t') { is_pp = false; break; } - } - if (is_pp) { - while (i < len) { - // Handle line-comment inside preprocessor - if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') { - syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); - break; - } - // Handle block comment start inside preprocessor - if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') { - state.in_block_comment = true; - syn_set_token(out, out_len, i, SYN_COMMENT); - syn_set_token(out, out_len, i + 1, SYN_COMMENT); - i += 2; - // Continue consuming as comment - while (i < len) { - if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { - syn_set_token(out, out_len, i, SYN_COMMENT); - syn_set_token(out, out_len, i + 1, SYN_COMMENT); - i += 2; - state.in_block_comment = false; - break; - } - syn_set_token(out, out_len, i, SYN_COMMENT); - i++; - } - continue; - } - syn_set_token(out, out_len, i, SYN_PREPROCESSOR); - i++; - } - continue; - } - } - - // ---- String literal ---- - if (c == '"') { - syn_set_token(out, out_len, i, SYN_STRING); - i++; - while (i < len) { - if (line[i] == '\\' && i + 1 < len) { - syn_set_token(out, out_len, i, SYN_STRING); - syn_set_token(out, out_len, i + 1, SYN_STRING); - i += 2; - continue; - } - if (line[i] == '"') { - syn_set_token(out, out_len, i, SYN_STRING); - i++; - break; - } - syn_set_token(out, out_len, i, SYN_STRING); - i++; - } - continue; - } - - // ---- Character literal ---- - if (c == '\'') { - syn_set_token(out, out_len, i, SYN_CHAR); - i++; - while (i < len) { - if (line[i] == '\\' && i + 1 < len) { - syn_set_token(out, out_len, i, SYN_CHAR); - syn_set_token(out, out_len, i + 1, SYN_CHAR); - i += 2; - continue; - } - if (line[i] == '\'') { - syn_set_token(out, out_len, i, SYN_CHAR); - i++; - break; - } - syn_set_token(out, out_len, i, SYN_CHAR); - i++; - } - continue; - } - - // ---- Numbers ---- - if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { - syn_consume_number(line, len, i, out, out_len, true, cpp); - continue; - } - - // ---- Identifiers / keywords / types / literal prefixes ---- - if (syn_is_alpha(c)) { - int start = i; - while (i < len && syn_is_alnum(line[i])) i++; - if (cpp && i < len && line[i] == '"' && - syn_is_raw_string_prefix(line + start, i - start)) { - syn_fill_tokens(out, out_len, start, i, SYN_STRING); - syn_consume_raw_string(line, len, i, out, out_len, state); - continue; - } - if (cpp && i < len && line[i] == '"' && - syn_is_string_prefix(line + start, i - start)) { - // The quote itself is handled on the next iteration - syn_fill_tokens(out, out_len, start, i, SYN_STRING); - continue; - } - if (cpp && i < len && line[i] == '\'' && - syn_is_string_prefix(line + start, i - start)) { - syn_fill_tokens(out, out_len, start, i, SYN_CHAR); - continue; - } - SynToken tok = syn_classify_word(lang, line + start, i - start); - syn_fill_tokens(out, out_len, start, i, tok); - continue; - } - - // ---- Operators ---- - if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || - c == '=' || c == '!' || c == '<' || c == '>' || c == '&' || - c == '|' || c == '^' || c == '~' || c == '?' || c == ':') { - syn_set_token(out, out_len, i, SYN_OPERATOR); - i++; - continue; - } - - // ---- Everything else (whitespace, braces, parens, etc.) ---- - syn_set_token(out, out_len, i, SYN_NORMAL); - i++; - } -} - -// ============================================================================ - -inline void syn_highlight_line_lua(const char* line, int len, SynToken* out, int out_len, - SynState& state) { - int i = 0; - while (i < len) { - if (state.long_token != SYN_NORMAL) { - int span = 0; - if (syn_match_lua_long_bracket_close(line, len, i, state.long_bracket_eqs, span)) { - syn_fill_tokens(out, out_len, i, i + span, state.long_token); - i += span; - state.long_token = SYN_NORMAL; - state.long_bracket_eqs = -1; - continue; - } - syn_set_token(out, out_len, i, state.long_token); - i++; - continue; - } - char c = line[i]; - if (c == '-' && i + 1 < len && line[i + 1] == '-') { - int eqs = 0; - int span = 0; - if (i + 2 < len && syn_match_lua_long_bracket_open(line, len, i + 2, eqs, span)) { - syn_fill_tokens(out, out_len, i, i + 2 + span, SYN_COMMENT); - i += 2 + span; - state.long_token = SYN_COMMENT; - state.long_bracket_eqs = eqs; - continue; - } - syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); - break; - } - if (c == '"' || c == '\'') { - char quote = c; - syn_set_token(out, out_len, i, SYN_STRING); - i++; - while (i < len) { - if (line[i] == '\\' && i + 1 < len) { - syn_set_token(out, out_len, i, SYN_STRING); - syn_set_token(out, out_len, i + 1, SYN_STRING); - i += 2; - continue; - } - syn_set_token(out, out_len, i, SYN_STRING); - if (line[i] == quote) { - i++; - break; - } - i++; - } - continue; - } - int eqs = 0; - int span = 0; - if (syn_match_lua_long_bracket_open(line, len, i, eqs, span)) { - syn_fill_tokens(out, out_len, i, i + span, SYN_STRING); - i += span; - state.long_token = SYN_STRING; - state.long_bracket_eqs = eqs; - continue; - } - if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { - syn_consume_number(line, len, i, out, out_len, 0); - continue; - } - if (syn_is_alpha(c)) { - int start = i; - while (i < len && syn_is_alnum(line[i])) i++; - SynToken tok = syn_classify_word(SYN_LANG_LUA, line + start, i - start); - syn_fill_tokens(out, out_len, start, i, tok); - continue; - } - if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || - c == '^' || c == '#' || c == '=' || c == '<' || c == '>' || - c == '~' || c == ':' || c == '.' || c == ',' || c == ';' || - c == '(' || c == ')' || c == '{' || c == '}' || - c == '[' || c == ']') { - syn_set_token(out, out_len, i, SYN_OPERATOR); - i++; - continue; - } - syn_set_token(out, out_len, i, SYN_NORMAL); - i++; - } -} - -// ============================================================================ -// Per-line highlighter -// ============================================================================ - -inline void syn_highlight_line(const char* line, int len, SynToken* out, int out_len, - SynLanguage lang, SynState& state) { - if (!line || len <= 0) return; - switch (lang) { - case SYN_LANG_C: - case SYN_LANG_CPP: - syn_highlight_line_c(line, len, out, out_len, state, lang); - break; - case SYN_LANG_LUA: - syn_highlight_line_lua(line, len, out, out_len, state); - break; - default: - syn_fill_tokens(out, out_len, 0, len, SYN_NORMAL); - break; - } -} - -// ============================================================================ -// Language detection -// ============================================================================ - -inline bool syn_path_ends_with(const char* path, const char* suffix) { - if (!path || !suffix) return false; - int path_len = 0; - while (path[path_len]) path_len++; - int suffix_len = 0; - while (suffix[suffix_len]) suffix_len++; - if (path_len < suffix_len) return false; - for (int i = 0; i < suffix_len; i++) { - if (path[path_len - suffix_len + i] != suffix[i]) - return false; - } - return true; -} - -inline SynLanguage syn_detect_language(const char* filepath) { - if (!filepath || filepath[0] == '\0') return SYN_LANG_NONE; - if (syn_path_ends_with(filepath, ".cpp") || syn_path_ends_with(filepath, ".cc") || - syn_path_ends_with(filepath, ".cxx") || syn_path_ends_with(filepath, ".c++") || - syn_path_ends_with(filepath, ".hpp") || syn_path_ends_with(filepath, ".hh") || - syn_path_ends_with(filepath, ".hxx") || syn_path_ends_with(filepath, ".h++") || - syn_path_ends_with(filepath, ".ipp") || syn_path_ends_with(filepath, ".tpp") || - syn_path_ends_with(filepath, ".inl")) - return SYN_LANG_CPP; - if (syn_path_ends_with(filepath, ".c") || syn_path_ends_with(filepath, ".h")) - return SYN_LANG_C; - if (syn_path_ends_with(filepath, ".lua")) - return SYN_LANG_LUA; - return SYN_LANG_NONE; -} \ No newline at end of file diff --git a/programs/include/syntax/lang_c.hpp b/programs/include/syntax/lang_c.hpp new file mode 100644 index 0000000..1163f77 --- /dev/null +++ b/programs/include/syntax/lang_c.hpp @@ -0,0 +1,321 @@ +/* + * lang_c.hpp + * C and C++ highlighting + * Shared by the MontaukOS text editor (GUI) and edit.elf (CLI) + * Copyright (c) 2026 Daniel Hammer +*/ + +// Included by ; not standalone. + +#pragma once + +// ============================================================================ +// Keyword / type classification +// ============================================================================ + +inline SynToken syn_classify_c_word(const char* buf, int len) { + static const char* keywords[] = { + "auto", "break", "case", "const", "continue", "default", "do", + "else", "enum", "extern", "for", "goto", "if", "inline", + "register", "restrict", "return", "sizeof", "static", "struct", + "switch", "typedef", "union", "volatile", "while", + "NULL", "true", "false", "nullptr", + }; + static const char* types[] = { + "void", "char", "short", "int", "long", "float", "double", + "signed", "unsigned", "bool", "_Bool", + "int8_t", "int16_t", "int32_t", "int64_t", + "uint8_t", "uint16_t", "uint32_t", "uint64_t", + "size_t", "ssize_t", "ptrdiff_t", "intptr_t", "uintptr_t", + "FILE", + }; + for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { + if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; + } + for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { + if (syn_streq(buf, len, types[i])) return SYN_TYPE; + } + return SYN_NORMAL; +} + +inline SynToken syn_classify_cpp_word(const char* buf, int len) { + // C++ keywords (on top of the C set, which is checked first) + static const char* keywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "bitand", "bitor", + "catch", "class", "compl", "concept", "consteval", "constexpr", + "constinit", "const_cast", "co_await", "co_return", "co_yield", + "decltype", "delete", "dynamic_cast", "explicit", "export", + "final", "friend", "mutable", "namespace", "new", "noexcept", + "not", "not_eq", "operator", "or", "or_eq", "override", + "private", "protected", "public", "reinterpret_cast", "requires", + "static_assert", "static_cast", "template", "this", "thread_local", + "throw", "try", "typeid", "typename", "using", "virtual", + "xor", "xor_eq", + }; + // C++ library / builtin types + static const char* types[] = { + "char8_t", "char16_t", "char32_t", "wchar_t", "nullptr_t", + "std", "string", "string_view", "vector", "array", "span", + "map", "set", "unordered_map", "unordered_set", "deque", "list", + "pair", "tuple", "optional", "variant", "function", + "unique_ptr", "shared_ptr", "weak_ptr", "initializer_list", + }; + + SynToken c = syn_classify_c_word(buf, len); + if (c != SYN_NORMAL) return c; + + for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { + if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; + } + for (int i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i++) { + if (syn_streq(buf, len, types[i])) return SYN_TYPE; + } + return SYN_NORMAL; +} + +// ============================================================================ +// C highlighting +// ============================================================================ +// C / C++ raw string helpers +// ============================================================================ + +inline bool syn_is_raw_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "R") || syn_streq(buf, len, "LR") || + syn_streq(buf, len, "uR") || syn_streq(buf, len, "UR") || + syn_streq(buf, len, "u8R"); +} + +inline bool syn_is_string_prefix(const char* buf, int len) { + return syn_streq(buf, len, "L") || syn_streq(buf, len, "u") || + syn_streq(buf, len, "U") || syn_streq(buf, len, "u8"); +} + +// Colors a raw-string body from `i` up to and including the closing )delim". +// If the terminator is not on this line, leaves state.in_raw_string set so the +// next line continues the literal. +inline void syn_consume_raw_body(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + while (i < len) { + if (line[i] == ')') { + int j = i + 1; + int k = 0; + while (k < state.raw_delim_len && j < len && line[j] == state.raw_delim[k]) { + j++; + k++; + } + if (k == state.raw_delim_len && j < len && line[j] == '"') { + syn_fill_tokens(out, out_len, i, j + 1, SYN_STRING); + i = j + 1; + state.in_raw_string = false; + state.raw_delim_len = 0; + return; + } + } + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } +} + +// Enters a raw string; `line[i]` must be the opening quote. +inline void syn_consume_raw_string(const char* line, int len, int& i, + SynToken* out, int out_len, SynState& state) { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.raw_delim_len = 0; + while (i < len && line[i] != '(' && state.raw_delim_len < SYN_RAW_DELIM_MAX) { + state.raw_delim[state.raw_delim_len++] = line[i]; + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } + if (i < len && line[i] == '(') { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + state.in_raw_string = true; + syn_consume_raw_body(line, len, i, out, out_len, state); + return; + } + // Malformed (or an over-long delimiter): treat the rest of the line as string + syn_fill_tokens(out, out_len, i, len, SYN_STRING); + i = len; +} + +// ============================================================================ +// C / C++ highlighting +// ============================================================================ + +inline void syn_highlight_line_c(const char* line, int len, SynToken* out, int out_len, + SynState& state, SynLanguage lang = SYN_LANG_C) { + const bool cpp = (lang == SYN_LANG_CPP); + int i = 0; + + while (i < len) { + // ---- Raw string continuation ---- + if (state.in_raw_string) { + syn_consume_raw_body(line, len, i, out, out_len, state); + continue; + } + + // ---- Block comment continuation ---- + if (state.in_block_comment) { + while (i < len) { + if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { + syn_set_token(out, out_len, i, SYN_COMMENT); + syn_set_token(out, out_len, i + 1, SYN_COMMENT); + i += 2; + state.in_block_comment = false; + break; + } + syn_set_token(out, out_len, i, SYN_COMMENT); + i++; + } + continue; + } + + char c = line[i]; + + // ---- Line comment ---- + if (c == '/' && i + 1 < len && line[i + 1] == '/') { + syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); + break; + } + + // ---- Block comment start ---- + if (c == '/' && i + 1 < len && line[i + 1] == '*') { + state.in_block_comment = true; + syn_set_token(out, out_len, i, SYN_COMMENT); + syn_set_token(out, out_len, i + 1, SYN_COMMENT); + i += 2; + continue; + } + + // ---- Preprocessor directive ---- + if (c == '#') { + // Check that only whitespace precedes the # + bool is_pp = true; + for (int j = 0; j < i; j++) { + if (line[j] != ' ' && line[j] != '\t') { is_pp = false; break; } + } + if (is_pp) { + while (i < len) { + // Handle line-comment inside preprocessor + if (i + 1 < len && line[i] == '/' && line[i + 1] == '/') { + syn_fill_tokens(out, out_len, i, len, SYN_COMMENT); + break; + } + // Handle block comment start inside preprocessor + if (i + 1 < len && line[i] == '/' && line[i + 1] == '*') { + state.in_block_comment = true; + syn_set_token(out, out_len, i, SYN_COMMENT); + syn_set_token(out, out_len, i + 1, SYN_COMMENT); + i += 2; + // Continue consuming as comment + while (i < len) { + if (i + 1 < len && line[i] == '*' && line[i + 1] == '/') { + syn_set_token(out, out_len, i, SYN_COMMENT); + syn_set_token(out, out_len, i + 1, SYN_COMMENT); + i += 2; + state.in_block_comment = false; + break; + } + syn_set_token(out, out_len, i, SYN_COMMENT); + i++; + } + continue; + } + syn_set_token(out, out_len, i, SYN_PREPROCESSOR); + i++; + } + continue; + } + } + + // ---- String literal ---- + if (c == '"') { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + while (i < len) { + if (line[i] == '\\' && i + 1 < len) { + syn_set_token(out, out_len, i, SYN_STRING); + syn_set_token(out, out_len, i + 1, SYN_STRING); + i += 2; + continue; + } + if (line[i] == '"') { + syn_set_token(out, out_len, i, SYN_STRING); + i++; + break; + } + syn_set_token(out, out_len, i, SYN_STRING); + i++; + } + continue; + } + + // ---- Character literal ---- + if (c == '\'') { + syn_set_token(out, out_len, i, SYN_CHAR); + i++; + while (i < len) { + if (line[i] == '\\' && i + 1 < len) { + syn_set_token(out, out_len, i, SYN_CHAR); + syn_set_token(out, out_len, i + 1, SYN_CHAR); + i += 2; + continue; + } + if (line[i] == '\'') { + syn_set_token(out, out_len, i, SYN_CHAR); + i++; + break; + } + syn_set_token(out, out_len, i, SYN_CHAR); + i++; + } + continue; + } + + // ---- Numbers ---- + if (syn_is_digit(c) || (c == '.' && i + 1 < len && syn_is_digit(line[i + 1]))) { + syn_consume_number(line, len, i, out, out_len, true, cpp); + continue; + } + + // ---- Identifiers / keywords / types / literal prefixes ---- + if (syn_is_alpha(c)) { + int start = i; + while (i < len && syn_is_alnum(line[i])) i++; + if (cpp && i < len && line[i] == '"' && + syn_is_raw_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + syn_consume_raw_string(line, len, i, out, out_len, state); + continue; + } + if (cpp && i < len && line[i] == '"' && + syn_is_string_prefix(line + start, i - start)) { + // The quote itself is handled on the next iteration + syn_fill_tokens(out, out_len, start, i, SYN_STRING); + continue; + } + if (cpp && i < len && line[i] == '\'' && + syn_is_string_prefix(line + start, i - start)) { + syn_fill_tokens(out, out_len, start, i, SYN_CHAR); + continue; + } + SynToken tok = syn_classify_word(lang, line + start, i - start); + syn_fill_tokens(out, out_len, start, i, tok); + continue; + } + + // ---- Operators ---- + if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || + c == '=' || c == '!' || c == '<' || c == '>' || c == '&' || + c == '|' || c == '^' || c == '~' || c == '?' || c == ':') { + syn_set_token(out, out_len, i, SYN_OPERATOR); + i++; + continue; + } + + // ---- Everything else (whitespace, braces, parens, etc.) ---- + syn_set_token(out, out_len, i, SYN_NORMAL); + i++; + } +} diff --git a/programs/include/syntax/lang_html.hpp b/programs/include/syntax/lang_html.hpp new file mode 100644 index 0000000..6904fde --- /dev/null +++ b/programs/include/syntax/lang_html.hpp @@ -0,0 +1,475 @@ +/* + * lang_html.hpp + * HTML highlighting, with embedded CSS and JS + * Shared by the MontaukOS text editor (GUI) and edit.elf (CLI) + * Copyright (c) 2026 Daniel Hammer +*/ + +// Included by ; not standalone. + +#pragma once + +// ============================================================================ +// JavaScript keyword / builtin classification +// ============================================================================ + +inline SynToken syn_classify_js_word(const char* buf, int len) { + static const char* keywords[] = { + "async", "await", "break", "case", "catch", "class", "const", + "continue", "debugger", "default", "delete", "do", "else", + "export", "extends", "finally", "for", "function", "if", + "import", "in", "instanceof", "let", "new", "of", "return", + "static", "super", "switch", "this", "throw", "try", "typeof", + "var", "void", "while", "with", "yield", + "true", "false", "null", "undefined", + }; + static const char* builtins[] = { + "Array", "Boolean", "Date", "Error", "Infinity", "JSON", "Map", + "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RegExp", + "Set", "String", "Symbol", "WeakMap", "WeakSet", + "console", "document", "globalThis", "localStorage", "navigator", + "window", "fetch", "setTimeout", "setInterval", + }; + for (int i = 0; i < (int)(sizeof(keywords) / sizeof(keywords[0])); i++) { + if (syn_streq(buf, len, keywords[i])) return SYN_KEYWORD; + } + for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) { + if (syn_streq(buf, len, builtins[i])) return SYN_TYPE; + } + return SYN_NORMAL; +} + +// ============================================================================ +// JavaScript (also used for begins. + +inline void syn_highlight_js_range(const char* line, int limit, int& i, + SynToken* out, int out_len, SynState& state) { + while (i < limit) { + // ---- Block comment continuation ---- + if (state.in_block_comment) { + while (i < limit) { + if (i + 1 < limit && line[i] == '*' && line[i + 1] == '/') { + syn_fill_tokens(out, out_len, i, i + 2, SYN_COMMENT); + i += 2; + state.in_block_comment = false; + break; + } + syn_set_token(out, out_len, i, SYN_COMMENT); + i++; + } + continue; + } + + char c = line[i]; + + if (c == '/' && i + 1 < limit && line[i + 1] == '/') { + syn_fill_tokens(out, out_len, i, limit, SYN_COMMENT); + i = limit; + break; + } + + if (c == '/' && i + 1 < limit && line[i + 1] == '*') { + state.in_block_comment = true; + syn_fill_tokens(out, out_len, i, i + 2, SYN_COMMENT); + i += 2; + continue; + } + + // ---- String / template literal (single line only) ---- + if (c == '"' || c == '\'' || c == '`') { + char quote = c; + syn_set_token(out, out_len, i, SYN_STRING); + i++; + while (i < limit) { + if (line[i] == '\\' && i + 1 < limit) { + syn_fill_tokens(out, out_len, i, i + 2, SYN_STRING); + i += 2; + continue; + } + syn_set_token(out, out_len, i, SYN_STRING); + if (line[i] == quote) { + i++; + break; + } + i++; + } + continue; + } + + if (syn_is_digit(c) || (c == '.' && i + 1 < limit && syn_is_digit(line[i + 1]))) { + syn_consume_number(line, limit, i, out, out_len, false); + continue; + } + + if (syn_is_alpha(c) || c == '$') { + int start = i; + while (i < limit && (syn_is_alnum(line[i]) || line[i] == '$')) i++; + syn_fill_tokens(out, out_len, start, i, + syn_classify_js_word(line + start, i - start)); + continue; + } + + if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || + c == '=' || c == '!' || c == '<' || c == '>' || c == '&' || + c == '|' || c == '^' || c == '~' || c == '?' || c == ':') { + syn_set_token(out, out_len, i, SYN_OPERATOR); + i++; + continue; + } + + syn_set_token(out, out_len, i, SYN_NORMAL); + i++; + } +} + +// ============================================================================ +// CSS (also used for