aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-18 08:02:20 +0000
committer_ov4 <ov4@ov4.org>2026-10-18 08:02:20 +0000
commit34a4998658674411ef3b5a392483990467a5d6e2 (patch)
treeace5773a4890ffb88d8f3338f9938d4dfa929804
parente7320359c12b97ccb85ebfb06b2540124630df7b (diff)
feat(lexer): accept identifiers starting with an underscore
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS/CONVENTIONS.md2
-rw-r--r--doc/language/cpsc-language-overview.7.md2
-rw-r--r--lib/Lexer/Lexer.cc11
-rw-r--r--tests/i/underscore-identifiers/case.json1
-rw-r--r--tests/i/underscore-identifiers/expected.txt1
-rw-r--r--tests/i/underscore-identifiers/program.cps9
-rw-r--r--tests/iii/reserved-prefix/case.json1
-rw-r--r--tests/iii/reserved-prefix/program.cps1
8 files changed, 26 insertions, 2 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md
index 41b049f..598ff76 100644
--- a/AGENTS/CONVENTIONS.md
+++ b/AGENTS/CONVENTIONS.md
@@ -207,3 +207,5 @@ is an LLVM GlobalVariable and must survive FunctionGen's symbol reset. User MAIN
case insensitive, takes no parameters and returns INTEGER; its LLVM name is
__cps_main with internal linkage. The generated i32 main wrapper calls it through
ExceptionHandler, truncates its result and retains the final uncaught handler.
+
+Identifiers may begin with an underscore. The case-insensitive `__cps_` prefix is reserved in every user identifier, including EXTERN declarations.
diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md
index ff3d1f0..96fb071 100644
--- a/doc/language/cpsc-language-overview.7.md
+++ b/doc/language/cpsc-language-overview.7.md
@@ -6,7 +6,7 @@ cpsc-language-overview - supported cpsc pseudocode syntax
# DESCRIPTION
-Keywords are uppercase and case-sensitive. Identifiers, including built-in function names, are case insensitive. Indentation is for readability; blocks use explicit closing keywords. Identifiers begin with a letter and continue with letters, digits, or underscores.
+Keywords are uppercase and case-sensitive. Identifiers, including built-in function names, are case insensitive. Indentation is for readability; blocks use explicit closing keywords. Identifiers begin with a letter or underscore and continue with letters, digits, or underscores.
Assign with ASCII `<-`: `Count <- Count + 1`. Compare with `=`, `<>`, `<`, `>`, `<=`, and `>=`. Built-in STRING comparisons compare contents, case-sensitively. Use `+`, `-`, `*`, and `/` for arithmetic; built-in `/` always returns REAL. Use `&` to join strings.
diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc
index edc77de..9435d04 100644
--- a/lib/Lexer/Lexer.cc
+++ b/lib/Lexer/Lexer.cc
@@ -124,11 +124,20 @@ int Lexer::readToken() {
return IsChar ? tok_char_literal : tok_string_literal;
}
- if (isalpha(LastChar)) {
+ if (isalpha(LastChar) || LastChar == '_') {
IdentifierStr = static_cast<char>(LastChar);
while (isalnum((LastChar = Input.get())) || LastChar == '_')
IdentifierStr += static_cast<char>(LastChar);
+ if (IdentifierStr.size() >= 6) {
+ std::string Prefix = IdentifierStr.substr(0, 6);
+ std::transform(Prefix.begin(), Prefix.end(), Prefix.begin(),
+ [](unsigned char C) { return std::tolower(C); });
+ if (Prefix == "__cps_") {
+ cps::sourceDiagnostic(TokenLine, "error: Reserved runtime identifier at line %d\n", TokenLine);
+ HadError = true;
+ }
+ }
if (IdentifierStr == "CONSTANT") return tok_constant;
if (IdentifierStr == "INCLUDE") return tok_include;
if (IdentifierStr == "INCLUDE_OBJ") return tok_include_obj;
diff --git a/tests/i/underscore-identifiers/case.json b/tests/i/underscore-identifiers/case.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tests/i/underscore-identifiers/case.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/i/underscore-identifiers/expected.txt b/tests/i/underscore-identifiers/expected.txt
new file mode 100644
index 0000000..d81cc07
--- /dev/null
+++ b/tests/i/underscore-identifiers/expected.txt
@@ -0,0 +1 @@
+42
diff --git a/tests/i/underscore-identifiers/program.cps b/tests/i/underscore-identifiers/program.cps
new file mode 100644
index 0000000..60d7b32
--- /dev/null
+++ b/tests/i/underscore-identifiers/program.cps
@@ -0,0 +1,9 @@
+CLASS _EXP_FOO
+ _Value : INTEGER
+ENDCLASS
+FUNCTION _Read(_Arg : INTEGER) RETURNS INTEGER
+ RETURN _Arg + 1
+ENDFUNCTION
+DECLARE _X : _EXP_FOO
+_X._Value <- _Read(41)
+OUTPUT _X._Value
diff --git a/tests/iii/reserved-prefix/case.json b/tests/iii/reserved-prefix/case.json
new file mode 100644
index 0000000..a28bf5d
--- /dev/null
+++ b/tests/iii/reserved-prefix/case.json
@@ -0,0 +1 @@
+{"errors":[{"reason":"Reserved runtime identifier","line":1}]}
diff --git a/tests/iii/reserved-prefix/program.cps b/tests/iii/reserved-prefix/program.cps
new file mode 100644
index 0000000..3a61a6b
--- /dev/null
+++ b/tests/iii/reserved-prefix/program.cps
@@ -0,0 +1 @@
+DECLARE __CpS_user_line : INTEGER