feat: refactor syntax higlighting support in edit.elf/Text Editor, add highlighting for html, css, js
This commit is contained in:
1 parent
5b90869101
commit
0a18b2cf2d
9 files changed
+1289
-1377
No files matched your search
@@ -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 <syntax/syntax.hpp>; 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++;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user