diff --git a/CHANGELOG.md b/CHANGELOG.md index be165ed9..c8f9841a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to Coderive are documented in this file. +## [v0.9.2] - Why Slow? - April 17, 2026 + +### 🔬 Lexer/Parser Throughput Baseline (New) +- Added a dedicated cross-language lexer/parser throughput suite under `benchmarks/lexer_parser/`. +- New runner: `bash benchmarks/lexer_parser/run_lexer_parser_benchmark.sh `. +- Current baseline (run: `5` medians, `1000` iterations): + +| Language | Median ms | Throughput MB/s | +|----------|-----------|-----------------| +| Coderive | 632 | 2.22 | +| Java | 111 | 12.61 | +| Go | 17 | 82.35 | +| Kotlin | 162 | 8.64 | +| Python | 270 | 5.19 | +| Lua | 228 | 6.14 | + +### 🧭 What This Suggests To Improve Next +- **Parser construction/validation overhead is dominant** in Coderive relative to pure scanner+light-parser baselines. +- **Tokenizer object churn remains significant** (token allocation and parser handoff pressure). +- **Near-term focus**: + 1. add a lexer-only throughput mode in Coderive benchmarks to isolate lexing cost from parse cost, + 2. reduce parser backtracking/rewind-heavy paths in `MainParser` declaration probing, + 3. introduce low-allocation token-stream views for parser hot paths. + ## [v0.9.0] - Platform Snapshot - April 13, 2026 ### 🔀 Merge Coverage for This Snapshot diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..35eb6ef2 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,53 @@ +# Cross-language benchmark suite + +This benchmark compares **Coderive** against: + +- Java +- C +- C++ +- Rust +- Python + +## Workload + +Each implementation runs the same deterministic kernels: + +1. Sum of squares from `1..2,000,000` +2. Iterative `fib(35)` repeated `2,000` times +3. Naive prime counting in `2..5,000` + +Each program prints a single line: + +`CHECKSUM:` + +Expected checksum: + +`2666668685121930669` + +## Run + +From repository root: + +```bash +bash benchmarks/run_cross_language_benchmark.sh +``` + +Optional runs per language (median-based): + +```bash +bash benchmarks/run_cross_language_benchmark.sh 5 +``` + +The runner: + +- compiles required binaries +- runs each language multiple times +- verifies checksum consistency +- prints median time in milliseconds +- skips languages missing toolchains + +## Lexer/Parser throughput benchmark + +See: + +`benchmarks/lexer_parser/README.md` diff --git a/benchmarks/c/cross_language_benchmark.c b/benchmarks/c/cross_language_benchmark.c new file mode 100644 index 00000000..0a47701b --- /dev/null +++ b/benchmarks/c/cross_language_benchmark.c @@ -0,0 +1,50 @@ +#include +#include + +static int64_t fib(int n) { + if (n == 0) return 0; + int64_t a = 0; + int64_t b = 1; + for (int i = 1; i <= n; i++) { + int64_t next = a + b; + a = b; + b = next; + } + return a; +} + +static int64_t sum_squares(int limit) { + int64_t total = 0; + for (int i = 1; i <= limit; i++) { + total += (int64_t)i * (int64_t)i; + } + return total; +} + +static int64_t prime_count(int limit) { + int64_t count = 0; + for (int candidate = 2; candidate <= limit; candidate++) { + int is_prime = 1; + if (candidate > 2) { + for (int divisor = 2; divisor < candidate; divisor++) { + if (candidate % divisor == 0) { + is_prime = 0; + break; + } + } + } + if (is_prime) { + count++; + } + } + return count; +} + +int main(void) { + int64_t sum_part = sum_squares(2000000); + int64_t fib_part = fib(35) * 2000; + int64_t prime_part = prime_count(5000); + int64_t checksum = sum_part + fib_part + prime_part; + printf("CHECKSUM:%lld\n", (long long)checksum); + return 0; +} diff --git a/benchmarks/coderive/CrossLanguageBenchmark.cod b/benchmarks/coderive/CrossLanguageBenchmark.cod new file mode 100644 index 00000000..cfbe76dd --- /dev/null +++ b/benchmarks/coderive/CrossLanguageBenchmark.cod @@ -0,0 +1,57 @@ +unit benchmarks.coderive + +share CrossLanguageBenchmark { + share fib(n: int) :: value: int { + if n == 0 { ~> (value: 0) } + a: int = 0 + b: int = 1 + for i of 1 to n { + nextValue := a + b + a = b + b = nextValue + } + ~> (value: a) + } + + share sumSquares(limit: int) :: value: int { + total: int = 0 + for i of 1 to limit { + total = total + (i * i) + } + ~> (value: total) + } + + share primeCount(limit: int) :: value: int { + count: int = 0 + for candidate of 2 to limit { + isPrime: bool = true + if candidate > 2 { + for divisor of 2 to candidate - 1 { + if candidate % divisor == 0 { + isPrime = false + break + } + } + } + if isPrime { + count = count + 1 + } + } + ~> (value: count) + } + + share main() { + sumPart := CrossLanguageBenchmark.sumSquares(2M) + + fibValue := CrossLanguageBenchmark.fib(35) + fibTotal: int = 0 + for i of 1 to 2000 { + fibTotal = fibTotal + fibValue + } + + primePart := CrossLanguageBenchmark.primeCount(5000) + + checksum := sumPart + fibTotal + primePart + out("CHECKSUM:" + checksum) + } +} diff --git a/benchmarks/cpp/cross_language_benchmark.cpp b/benchmarks/cpp/cross_language_benchmark.cpp new file mode 100644 index 00000000..0395caf4 --- /dev/null +++ b/benchmarks/cpp/cross_language_benchmark.cpp @@ -0,0 +1,50 @@ +#include +#include + +static std::int64_t fib(int n) { + if (n == 0) return 0; + std::int64_t a = 0; + std::int64_t b = 1; + for (int i = 1; i <= n; ++i) { + std::int64_t next = a + b; + a = b; + b = next; + } + return a; +} + +static std::int64_t sumSquares(int limit) { + std::int64_t total = 0; + for (int i = 1; i <= limit; ++i) { + total += static_cast(i) * static_cast(i); + } + return total; +} + +static std::int64_t primeCount(int limit) { + std::int64_t count = 0; + for (int candidate = 2; candidate <= limit; ++candidate) { + bool isPrime = true; + if (candidate > 2) { + for (int divisor = 2; divisor < candidate; ++divisor) { + if (candidate % divisor == 0) { + isPrime = false; + break; + } + } + } + if (isPrime) { + ++count; + } + } + return count; +} + +int main() { + std::int64_t sumPart = sumSquares(2000000); + std::int64_t fibPart = fib(35) * 2000; + std::int64_t primePart = primeCount(5000); + std::int64_t checksum = sumPart + fibPart + primePart; + std::cout << "CHECKSUM:" << checksum << '\n'; + return 0; +} diff --git a/benchmarks/java/CrossLanguageBenchmark.java b/benchmarks/java/CrossLanguageBenchmark.java new file mode 100644 index 00000000..ff7b438f --- /dev/null +++ b/benchmarks/java/CrossLanguageBenchmark.java @@ -0,0 +1,48 @@ +public final class CrossLanguageBenchmark { + private CrossLanguageBenchmark() {} + + private static long fib(int n) { + if (n == 0) return 0L; + long a = 0L; + long b = 1L; + for (int i = 1; i <= n; i++) { + long next = a + b; + a = b; + b = next; + } + return a; + } + + private static long sumSquares(int limit) { + long total = 0L; + for (int i = 1; i <= limit; i++) { + total += (long) i * (long) i; + } + return total; + } + + private static long primeCount(int limit) { + long count = 0L; + for (int candidate = 2; candidate <= limit; candidate++) { + boolean isPrime = true; + if (candidate > 2) { + for (int divisor = 2; divisor < candidate; divisor++) { + if (candidate % divisor == 0) { + isPrime = false; + break; + } + } + } + if (isPrime) count++; + } + return count; + } + + public static void main(String[] args) { + long sumPart = sumSquares(2_000_000); + long fibPart = fib(35) * 2_000L; + long primePart = primeCount(5_000); + long checksum = sumPart + fibPart + primePart; + System.out.println("CHECKSUM:" + checksum); + } +} diff --git a/benchmarks/lexer_parser/README.md b/benchmarks/lexer_parser/README.md new file mode 100644 index 00000000..1b34c161 --- /dev/null +++ b/benchmarks/lexer_parser/README.md @@ -0,0 +1,32 @@ +# Lexer/Parser Throughput Benchmark + +This suite benchmarks lexer/parser throughput for: + +- Coderive (real `MainLexer` + `MainParser`) +- Java +- Go +- Kotlin +- Python +- Lua + +## Run + +From repository root: + +```bash +bash benchmarks/lexer_parser/run_lexer_parser_benchmark.sh +``` + +Optional: + +```bash +bash benchmarks/lexer_parser/run_lexer_parser_benchmark.sh +``` + +- `runs`: median sample count (default `3`) +- `iterations`: full corpus passes per run (default `20`) + +Output columns: + +- **Median ms**: median wall time per language +- **Throughput MB/s**: processed corpus bytes per second diff --git a/benchmarks/lexer_parser/go/lexer_parser_bench.go b/benchmarks/lexer_parser/go/lexer_parser_bench.go new file mode 100644 index 00000000..0e526d6b --- /dev/null +++ b/benchmarks/lexer_parser/go/lexer_parser_bench.go @@ -0,0 +1,148 @@ +package main + +import ( +"bufio" +"fmt" +"os" +"strconv" +"strings" +) + +func isAlpha(c byte) bool { +return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +} + +func isDigit(c byte) bool { +return c >= '0' && c <= '9' +} + +func lexParse(text string) uint64 { +n := len(text) +i := 0 +var tokens, stmts, depth, maxDepth, kindSum uint64 + +for i < n { +c := text[i] +if c == ' ' || c == '\t' || c == '\r' || c == '\n' { +if c == '\n' { +stmts++ +} +i++ +continue +} +if c == '/' && i+1 < n && text[i+1] == '/' { +i += 2 +for i < n && text[i] != '\n' { +i++ +} +continue +} +if c == '/' && i+1 < n && text[i+1] == '*' { +i += 2 +for i+1 < n && !(text[i] == '*' && text[i+1] == '/') { +i++ +} +i += 2 +if i > n { +i = n +} +continue +} +if isAlpha(c) { +start := i +i++ +for i < n && (isAlpha(text[i]) || isDigit(text[i])) { +i++ +} +tokens++ +kindSum += uint64((i - start) % 97) +continue +} +if isDigit(c) { +i++ +for i < n && (isDigit(text[i]) || text[i] == '.') { +i++ +} +tokens++ +kindSum += 3 +continue +} +if c == '"' || c == '\'' { +quote := c +i++ +for i < n { +d := text[i] +if d == '\\' { +i += 2 +continue +} +if d == quote { +i++ +break +} +i++ +} +tokens++ +kindSum += 7 +continue +} +if c == '(' || c == '[' || c == '{' { +depth++ +if depth > maxDepth { +maxDepth = depth +} +} else if (c == ')' || c == ']' || c == '}') && depth > 0 { +depth-- +} +if c == ';' { +stmts++ +} +tokens++ +kindSum++ +i++ +} +return tokens*31 + stmts*17 + depth*13 + maxDepth*7 + kindSum +} + +func main() { +if len(os.Args) < 3 { +fmt.Fprintln(os.Stderr, "usage: lexer_parser_bench ") +os.Exit(2) +} +fileList := os.Args[1] +iterations, err := strconv.Atoi(os.Args[2]) +if err != nil { +panic(err) +} + +file, err := os.Open(fileList) +if err != nil { +panic(err) +} +defer file.Close() + +var paths []string +scanner := bufio.NewScanner(file) +for scanner.Scan() { +line := strings.TrimSpace(scanner.Text()) +if line != "" { +paths = append(paths, line) +} +} +if err := scanner.Err(); err != nil { +panic(err) +} + +var digest uint64 = 1469598103934665603 +for i := 0; i < iterations; i++ { +for _, p := range paths { +b, err := os.ReadFile(p) +if err != nil { +panic(err) +} +digest = digest*1315423911 + lexParse(string(b)) +} +} + +fmt.Printf("DIGEST:%d\n", digest) +} diff --git a/benchmarks/lexer_parser/java/CoderiveLexerParserBenchmark.java b/benchmarks/lexer_parser/java/CoderiveLexerParserBenchmark.java new file mode 100644 index 00000000..ace18540 --- /dev/null +++ b/benchmarks/lexer_parser/java/CoderiveLexerParserBenchmark.java @@ -0,0 +1,66 @@ +package benchmarks.lexer_parser.java; + +import cod.ast.node.Program; +import cod.lexer.MainLexer; +import cod.lexer.Token; +import cod.parser.MainParser; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public final class CoderiveLexerParserBenchmark { + private static long mix(long digest, long value) { + return digest * 1315423911L + value; + } + + private static List readPaths(String fileList) throws Exception { + List out = new ArrayList(); + BufferedReader reader = new BufferedReader(new FileReader(fileList)); + try { + String line; + while ((line = reader.readLine()) != null) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) out.add(trimmed); + } + } finally { + reader.close(); + } + return out; + } + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.err.println("usage: CoderiveLexerParserBenchmark "); + System.exit(2); + } + + String fileList = args[0]; + int iterations = Integer.parseInt(args[1]); + List paths = readPaths(fileList); + + long digest = 1469598103934665603L; + for (int i = 0; i < iterations; i++) { + for (String p : paths) { + String source = new String(Files.readAllBytes(Paths.get(p)), StandardCharsets.UTF_8); + MainLexer lexer = new MainLexer(source); + List tokens = lexer.tokenize(); + MainParser parser = new MainParser(tokens); + Program program = parser.parseProgram(); + + long value = tokens.size(); + if (program != null && program.unit != null) { + value += (program.unit.types != null ? program.unit.types.size() : 0) * 17L; + value += (program.unit.policies != null ? program.unit.policies.size() : 0) * 29L; + } + digest = mix(digest, value); + } + } + + System.out.println("DIGEST:" + Long.toUnsignedString(digest)); + } +} diff --git a/benchmarks/lexer_parser/java/JavaLexerParserBenchmark.java b/benchmarks/lexer_parser/java/JavaLexerParserBenchmark.java new file mode 100644 index 00000000..0bd3d372 --- /dev/null +++ b/benchmarks/lexer_parser/java/JavaLexerParserBenchmark.java @@ -0,0 +1,143 @@ +package benchmarks.lexer_parser.java; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public final class JavaLexerParserBenchmark { + private static boolean isAlpha(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; + } + + private static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + private static long mix(long digest, long value) { + return digest * 1315423911L + value; + } + + private static long lexParse(String text) { + int n = text.length(); + int i = 0; + long tokens = 0; + long stmts = 0; + long depth = 0; + long maxDepth = 0; + long kindSum = 0; + + while (i < n) { + char c = text.charAt(i); + + if (Character.isWhitespace(c)) { + if (c == '\n') stmts++; + i++; + continue; + } + + if (c == '/' && i + 1 < n && text.charAt(i + 1) == '/') { + i += 2; + while (i < n && text.charAt(i) != '\n') i++; + continue; + } + + if (c == '/' && i + 1 < n && text.charAt(i + 1) == '*') { + i += 2; + while (i + 1 < n && !(text.charAt(i) == '*' && text.charAt(i + 1) == '/')) i++; + i = Math.min(i + 2, n); + continue; + } + + if (isAlpha(c)) { + int start = i; + i++; + while (i < n && (isAlpha(text.charAt(i)) || isDigit(text.charAt(i)))) i++; + tokens++; + kindSum += (i - start) % 97; + continue; + } + + if (isDigit(c)) { + i++; + while (i < n && (isDigit(text.charAt(i)) || text.charAt(i) == '.')) i++; + tokens++; + kindSum += 3; + continue; + } + + if (c == '"' || c == '\'') { + char quote = c; + i++; + while (i < n) { + char d = text.charAt(i); + if (d == '\\') { + i += 2; + continue; + } + if (d == quote) { + i++; + break; + } + i++; + } + tokens++; + kindSum += 7; + continue; + } + + if (c == '(' || c == '[' || c == '{') { + depth++; + if (depth > maxDepth) maxDepth = depth; + } else if ((c == ')' || c == ']' || c == '}') && depth > 0) { + depth--; + } + if (c == ';') stmts++; + + tokens++; + kindSum++; + i++; + } + + return (((tokens * 31 + stmts * 17 + depth * 13 + maxDepth * 7) + kindSum)); + } + + private static List readPaths(String fileList) throws Exception { + List out = new ArrayList(); + BufferedReader reader = new BufferedReader(new FileReader(fileList)); + try { + String line; + while ((line = reader.readLine()) != null) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) out.add(trimmed); + } + } finally { + reader.close(); + } + return out; + } + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.err.println("usage: JavaLexerParserBenchmark "); + System.exit(2); + } + + String fileList = args[0]; + int iterations = Integer.parseInt(args[1]); + List paths = readPaths(fileList); + long digest = 1469598103934665603L; + + for (int i = 0; i < iterations; i++) { + for (String p : paths) { + String text = new String(Files.readAllBytes(Paths.get(p)), StandardCharsets.UTF_8); + digest = mix(digest, lexParse(text)); + } + } + + System.out.println("DIGEST:" + Long.toUnsignedString(digest)); + } +} diff --git a/benchmarks/lexer_parser/kotlin/LexerParserBench.kt b/benchmarks/lexer_parser/kotlin/LexerParserBench.kt new file mode 100644 index 00000000..22b7ce9d --- /dev/null +++ b/benchmarks/lexer_parser/kotlin/LexerParserBench.kt @@ -0,0 +1,106 @@ +import java.io.File + +private fun isAlpha(c: Char): Boolean = c == '_' || c in 'a'..'z' || c in 'A'..'Z' +private fun isDigit(c: Char): Boolean = c in '0'..'9' + +private fun lexParse(text: String): Long { + var i = 0 + val n = text.length + var tokens = 0L + var stmts = 0L + var depth = 0L + var maxDepth = 0L + var kindSum = 0L + + while (i < n) { + val c = text[i] + + if (c.isWhitespace()) { + if (c == '\n') stmts++ + i++ + continue + } + + if (c == '/' && i + 1 < n && text[i + 1] == '/') { + i += 2 + while (i < n && text[i] != '\n') i++ + continue + } + + if (c == '/' && i + 1 < n && text[i + 1] == '*') { + i += 2 + while (i + 1 < n && !(text[i] == '*' && text[i + 1] == '/')) i++ + i = minOf(i + 2, n) + continue + } + + if (isAlpha(c)) { + val start = i + i++ + while (i < n && (isAlpha(text[i]) || isDigit(text[i]))) i++ + tokens++ + kindSum += (i - start) % 97 + continue + } + + if (isDigit(c)) { + i++ + while (i < n && (isDigit(text[i]) || text[i] == '.')) i++ + tokens++ + kindSum += 3 + continue + } + + if (c == '"' || c == '\'') { + val quote = c + i++ + while (i < n) { + val d = text[i] + if (d == '\\') { + i += 2 + continue + } + if (d == quote) { + i++ + break + } + i++ + } + tokens++ + kindSum += 7 + continue + } + + if (c == '(' || c == '[' || c == '{') { + depth++ + if (depth > maxDepth) maxDepth = depth + } else if ((c == ')' || c == ']' || c == '}') && depth > 0) { + depth-- + } + if (c == ';') stmts++ + + tokens++ + kindSum++ + i++ + } + + return tokens * 31 + stmts * 17 + depth * 13 + maxDepth * 7 + kindSum +} + +fun main(args: Array) { + require(args.size >= 2) { "usage: LexerParserBench " } + + val fileList = File(args[0]) + val iterations = args[1].toInt() + val paths = fileList.readLines().map { it.trim() }.filter { it.isNotEmpty() } + + var digest = 1469598103934665603L + repeat(iterations) { + for (p in paths) { + val text = File(p).readText() + digest = digest * 1315423911L + lexParse(text) + } + } + + println("DIGEST:${java.lang.Long.toUnsignedString(digest)}") +} diff --git a/benchmarks/lexer_parser/lua/lexer_parser_bench.lua b/benchmarks/lexer_parser/lua/lexer_parser_bench.lua new file mode 100644 index 00000000..e5f20eba --- /dev/null +++ b/benchmarks/lexer_parser/lua/lexer_parser_bench.lua @@ -0,0 +1,103 @@ +local function is_alpha(c) + return c == '_' or (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') +end + +local function is_digit(c) + return c >= '0' and c <= '9' +end + +local function lex_parse(text) + local i = 1 + local n = #text + local tokens, stmts, depth, max_depth, kind_sum = 0, 0, 0, 0, 0 + + while i <= n do + local c = text:sub(i, i) + + if c:match('%s') then + if c == '\n' then stmts = stmts + 1 end + i = i + 1 + elseif c == '/' and i + 1 <= n and text:sub(i + 1, i + 1) == '/' then + i = i + 2 + while i <= n and text:sub(i, i) ~= '\n' do i = i + 1 end + elseif c == '/' and i + 1 <= n and text:sub(i + 1, i + 1) == '*' then + i = i + 2 + while i + 1 <= n and not (text:sub(i, i) == '*' and text:sub(i + 1, i + 1) == '/') do i = i + 1 end + i = math.min(i + 2, n + 1) + elseif is_alpha(c) then + local start = i + i = i + 1 + while i <= n do + local d = text:sub(i, i) + if not (is_alpha(d) or is_digit(d)) then break end + i = i + 1 + end + tokens = tokens + 1 + kind_sum = kind_sum + ((i - start) % 97) + elseif is_digit(c) then + i = i + 1 + while i <= n do + local d = text:sub(i, i) + if not (is_digit(d) or d == '.') then break end + i = i + 1 + end + tokens = tokens + 1 + kind_sum = kind_sum + 3 + elseif c == '"' or c == "'" then + local quote = c + i = i + 1 + while i <= n do + local d = text:sub(i, i) + if d == '\\' then + i = i + 2 + elseif d == quote then + i = i + 1 + break + else + i = i + 1 + end + end + tokens = tokens + 1 + kind_sum = kind_sum + 7 + else + if c == '(' or c == '[' or c == '{' then + depth = depth + 1 + if depth > max_depth then max_depth = depth end + elseif (c == ')' or c == ']' or c == '}') and depth > 0 then + depth = depth - 1 + end + if c == ';' then stmts = stmts + 1 end + tokens = tokens + 1 + kind_sum = kind_sum + 1 + i = i + 1 + end + end + + return tokens * 31 + stmts * 17 + depth * 13 + max_depth * 7 + kind_sum +end + +if #arg < 2 then + io.stderr:write('usage: lexer_parser_bench.lua \n') + os.exit(2) +end + +local file_list = arg[1] +local iterations = tonumber(arg[2]) + +local paths = {} +for line in io.lines(file_list) do + line = line:gsub('^%s+', ''):gsub('%s+$', '') + if line ~= '' then table.insert(paths, line) end +end + +local digest = 1469598103934665603 +for _ = 1, iterations do + for _, p in ipairs(paths) do + local f = assert(io.open(p, 'rb')) + local text = f:read('*a') + f:close() + digest = (digest * 1315423911 + lex_parse(text)) % 18446744073709551616 + end +end + +print(string.format('DIGEST:%.0f', digest)) diff --git a/benchmarks/lexer_parser/python/lexer_parser_bench.py b/benchmarks/lexer_parser/python/lexer_parser_bench.py new file mode 100755 index 00000000..60a40a17 --- /dev/null +++ b/benchmarks/lexer_parser/python/lexer_parser_bench.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import sys + + +def is_alpha(c: str) -> bool: + return c == '_' or ('a' <= c <= 'z') or ('A' <= c <= 'Z') + + +def is_digit(c: str) -> bool: + return '0' <= c <= '9' + + +def lex_parse(text: str) -> int: + i = 0 + n = len(text) + tokens = 0 + stmts = 0 + depth = 0 + max_depth = 0 + kind_sum = 0 + + while i < n: + c = text[i] + + if c.isspace(): + if c == '\n': + stmts += 1 + i += 1 + continue + + if c == '/' and i + 1 < n and text[i + 1] == '/': + i += 2 + while i < n and text[i] != '\n': + i += 1 + continue + + if c == '/' and i + 1 < n and text[i + 1] == '*': + i += 2 + while i + 1 < n and not (text[i] == '*' and text[i + 1] == '/'): + i += 1 + i = min(i + 2, n) + continue + + if is_alpha(c): + start = i + i += 1 + while i < n and (is_alpha(text[i]) or is_digit(text[i])): + i += 1 + tokens += 1 + kind_sum += (i - start) % 97 + continue + + if is_digit(c): + i += 1 + while i < n and (is_digit(text[i]) or text[i] == '.'): + i += 1 + tokens += 1 + kind_sum += 3 + continue + + if c == '"' or c == "'": + quote = c + i += 1 + while i < n: + d = text[i] + if d == '\\': + i += 2 + continue + if d == quote: + i += 1 + break + i += 1 + tokens += 1 + kind_sum += 7 + continue + + if c in '([{': + depth += 1 + if depth > max_depth: + max_depth = depth + elif c in ')]}' and depth > 0: + depth -= 1 + + if c == ';': + stmts += 1 + + tokens += 1 + kind_sum += 1 + i += 1 + + return tokens * 31 + stmts * 17 + depth * 13 + max_depth * 7 + kind_sum + + +def main() -> None: + if len(sys.argv) < 3: + print('usage: lexer_parser_bench.py ', file=sys.stderr) + raise SystemExit(2) + + file_list = sys.argv[1] + iterations = int(sys.argv[2]) + + with open(file_list, 'r', encoding='utf-8') as f: + paths = [line.strip() for line in f if line.strip()] + + digest = 1469598103934665603 + mask = (1 << 64) - 1 + + for _ in range(iterations): + for p in paths: + with open(p, 'r', encoding='utf-8') as f: + text = f.read() + digest = ((digest * 1315423911) + lex_parse(text)) & mask + + print(f'DIGEST:{digest}') + + +if __name__ == '__main__': + main() diff --git a/benchmarks/lexer_parser/run_lexer_parser_benchmark.sh b/benchmarks/lexer_parser/run_lexer_parser_benchmark.sh new file mode 100755 index 00000000..52141674 --- /dev/null +++ b/benchmarks/lexer_parser/run_lexer_parser_benchmark.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +BENCH_DIR="$ROOT_DIR/benchmarks/lexer_parser" +WORK_DIR="/tmp/coderive-lexer-parser-bench" +RUNS="${1:-3}" +ITERATIONS="${2:-20}" + +mkdir -p "$WORK_DIR" + +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +extract_digest() { + local output="$1" + local line + line="$(printf '%s\n' "$output" | grep '^DIGEST:' | tail -n 1 || true)" + if [[ -z "$line" ]]; then + return 1 + fi + printf '%s' "${line#DIGEST:}" +} + +median_ms() { + local values=("$@") + local sorted + sorted="$(printf '%s\n' "${values[@]}" | sort -n)" + local count + count="$(printf '%s\n' "$sorted" | wc -l | tr -d ' ')" + local index=$(( (count + 1) / 2 )) + printf '%s\n' "$sorted" | sed -n "${index}p" +} + +run_many() { + local lang="$1" + shift + local -a command=("$@") + local -a times=() + local baseline_digest="" + + local i + for ((i=1; i<=RUNS; i++)); do + local start_ns end_ns elapsed_ns elapsed_ms output digest + start_ns="$(date +%s%N)" + output="$("${command[@]}")" + end_ns="$(date +%s%N)" + elapsed_ns=$((end_ns - start_ns)) + elapsed_ms=$((elapsed_ns / 1000000)) + digest="$(extract_digest "$output")" || { + echo "[${lang}] ERROR: missing DIGEST output" + echo "$output" + return 1 + } + if [[ -z "$baseline_digest" ]]; then + baseline_digest="$digest" + elif [[ "$digest" != "$baseline_digest" ]]; then + echo "[${lang}] ERROR: non-deterministic digest (got ${digest}, expected ${baseline_digest})" + return 1 + fi + times+=("$elapsed_ms") + done + + local median + median="$(median_ms "${times[@]}")" + echo "${lang}|${median}" +} + +total_bytes() { + awk '{sum += $1} END {print sum}' +} + +echo "Lexer/parser throughput benchmark (runs: ${RUNS}, iterations per run: ${ITERATIONS})" + +{ + find "$ROOT_DIR/benchmarks/coderive" -type f -name '*.cod' +} | sort > "$WORK_DIR/files.txt" +if [[ ! -s "$WORK_DIR/files.txt" ]]; then + echo "No .cod files found under src/main/cod" + exit 1 +fi + +BYTES_PER_ITER="$(xargs -d '\n' wc -c < "$WORK_DIR/files.txt" | total_bytes)" +if [[ -z "$BYTES_PER_ITER" || "$BYTES_PER_ITER" -le 0 ]]; then + echo "Failed to compute corpus size" + exit 1 +fi +TOTAL_BYTES=$((BYTES_PER_ITER * ITERATIONS)) + +rm -rf "$WORK_DIR/coderive-java" "$WORK_DIR/java-bin" "$WORK_DIR/go-bin" "$WORK_DIR/kotlin" +mkdir -p "$WORK_DIR/coderive-java" "$WORK_DIR/java-bin" "$WORK_DIR/kotlin" + +javac -d "$WORK_DIR/coderive-java" $(find "$ROOT_DIR/src/main/java" -name '*.java') \ + "$BENCH_DIR/java/CoderiveLexerParserBenchmark.java" + +if has_cmd javac; then + javac -d "$WORK_DIR/java-bin" "$BENCH_DIR/java/JavaLexerParserBenchmark.java" +fi + +if has_cmd go; then + go build -o "$WORK_DIR/go-bin" "$BENCH_DIR/go/lexer_parser_bench.go" +fi + +if has_cmd kotlinc; then + kotlinc "$BENCH_DIR/kotlin/LexerParserBench.kt" -include-runtime -d "$WORK_DIR/kotlin/bench.jar" +fi + +echo +printf "%-10s | %-9s | %s\n" "Language" "Median ms" "Throughput MB/s" +echo "-----------|-----------|----------------" + +print_row() { + local lang="$1" + local median="$2" + local throughput + throughput="$(awk -v bytes="$TOTAL_BYTES" -v ms="$median" 'BEGIN { if (ms <= 0) print "inf"; else printf "%.2f", (bytes / 1000000.0) / (ms / 1000.0) }')" + printf "%-10s | %-9s | %s\n" "$lang" "$median" "$throughput" +} + +row="$(run_many "Coderive" java -cp "$WORK_DIR/coderive-java" benchmarks.lexer_parser.java.CoderiveLexerParserBenchmark "$WORK_DIR/files.txt" "$ITERATIONS")" +print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" + +if [[ -f "$WORK_DIR/java-bin/benchmarks/lexer_parser/java/JavaLexerParserBenchmark.class" ]]; then + row="$(run_many "Java" java -cp "$WORK_DIR/java-bin" benchmarks.lexer_parser.java.JavaLexerParserBenchmark "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +fi + +if [[ -x "$WORK_DIR/go-bin" ]]; then + row="$(run_many "Go" "$WORK_DIR/go-bin" "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +fi + +if [[ -f "$WORK_DIR/kotlin/bench.jar" ]]; then + row="$(run_many "Kotlin" java -jar "$WORK_DIR/kotlin/bench.jar" "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +fi + +if has_cmd python3; then + row="$(run_many "Python" python3 "$BENCH_DIR/python/lexer_parser_bench.py" "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +fi + +if has_cmd lua; then + row="$(run_many "Lua" lua "$BENCH_DIR/lua/lexer_parser_bench.lua" "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +elif has_cmd lua5.4; then + row="$(run_many "Lua" lua5.4 "$BENCH_DIR/lua/lexer_parser_bench.lua" "$WORK_DIR/files.txt" "$ITERATIONS")" + print_row "$(echo "$row" | cut -d'|' -f1)" "$(echo "$row" | cut -d'|' -f2)" +else + echo "[skip] Lua benchmark: lua interpreter not found" +fi diff --git a/benchmarks/python/cross_language_benchmark.py b/benchmarks/python/cross_language_benchmark.py new file mode 100644 index 00000000..8e609ac0 --- /dev/null +++ b/benchmarks/python/cross_language_benchmark.py @@ -0,0 +1,43 @@ +def fib(n: int) -> int: + if n == 0: + return 0 + a = 0 + b = 1 + for _ in range(1, n + 1): + nxt = a + b + a = b + b = nxt + return a + + +def sum_squares(limit: int) -> int: + total = 0 + for i in range(1, limit + 1): + total += i * i + return total + + +def prime_count(limit: int) -> int: + count = 0 + for candidate in range(2, limit + 1): + is_prime = True + if candidate > 2: + for divisor in range(2, candidate): + if candidate % divisor == 0: + is_prime = False + break + if is_prime: + count += 1 + return count + + +def main() -> None: + sum_part = sum_squares(2_000_000) + fib_part = fib(35) * 2_000 + prime_part = prime_count(5_000) + checksum = sum_part + fib_part + prime_part + print(f"CHECKSUM:{checksum}") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/run_cross_language_benchmark.sh b/benchmarks/run_cross_language_benchmark.sh new file mode 100644 index 00000000..7b29c70c --- /dev/null +++ b/benchmarks/run_cross_language_benchmark.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BENCH_DIR="$ROOT_DIR/benchmarks" +WORK_DIR="/tmp/coderive-cross-bench" +RUNS="${1:-3}" +# Deterministic checksum combining: sum of squares (1..2M), repeated fib(35) x2000, +# and prime count (2..5000). Update only when workload logic changes across implementations. +EXPECTED_CHECKSUM="2666668685121930669" + +mkdir -p "$WORK_DIR" + +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +extract_checksum() { + local output="$1" + local line + line="$(printf '%s\n' "$output" | grep '^CHECKSUM:' | tail -n 1 || true)" + if [[ -z "$line" ]]; then + return 1 + fi + printf '%s' "${line#CHECKSUM:}" +} + +median_ms() { + local values=("$@") + local sorted + sorted="$(printf '%s\n' "${values[@]}" | sort -n)" + local count + count="$(printf '%s\n' "$sorted" | wc -l | tr -d ' ')" + local index=$(( (count + 1) / 2 )) + printf '%s\n' "$sorted" | sed -n "${index}p" +} + +run_many() { + local lang="$1" + shift + local -a command=("$@") + local -a times=() + + local i + for ((i=1; i<=RUNS; i++)); do + local start_ns end_ns elapsed_ns output checksum elapsed_ms + start_ns="$(date +%s%N)" + output="$("${command[@]}")" + end_ns="$(date +%s%N)" + elapsed_ns=$((end_ns - start_ns)) + elapsed_ms=$((elapsed_ns / 1000000)) + checksum="$(extract_checksum "$output")" || { + echo "[${lang}] ERROR: missing CHECKSUM output" + echo "$output" + return 1 + } + if [[ "$checksum" != "$EXPECTED_CHECKSUM" ]]; then + echo "[${lang}] ERROR: checksum mismatch (got ${checksum}, expected ${EXPECTED_CHECKSUM})" + return 1 + fi + times+=("$elapsed_ms") + done + + local median + median="$(median_ms "${times[@]}")" + echo "${lang}|${median}" +} + +echo "Cross-language benchmark (runs per language: ${RUNS})" +echo "Expected checksum: ${EXPECTED_CHECKSUM}" +echo + +# Build Coderive runtime for benchmark execution. +echo "[setup] compiling Coderive Java runtime..." +rm -rf "$WORK_DIR/coderive-java" "$WORK_DIR/java-bin" "$WORK_DIR/c-bin" "$WORK_DIR/cpp-bin" "$WORK_DIR/rust-bin" +mkdir -p "$WORK_DIR/coderive-java" "$WORK_DIR/java-bin" +javac -d "$WORK_DIR/coderive-java" $(find "$ROOT_DIR/src/main/java" -name '*.java') + +# Build language-specific binaries where toolchains exist. +if has_cmd javac; then + javac -d "$WORK_DIR/java-bin" "$BENCH_DIR/java/CrossLanguageBenchmark.java" +else + echo "[skip] Java benchmark: javac not found" +fi + +if has_cmd gcc; then + gcc -O3 -std=c11 "$BENCH_DIR/c/cross_language_benchmark.c" -o "$WORK_DIR/c-bin" +else + echo "[skip] C benchmark: gcc not found" +fi + +if has_cmd g++; then + g++ -O3 -std=c++17 "$BENCH_DIR/cpp/cross_language_benchmark.cpp" -o "$WORK_DIR/cpp-bin" +else + echo "[skip] C++ benchmark: g++ not found" +fi + +if has_cmd rustc; then + rustc -O "$BENCH_DIR/rust/cross_language_benchmark.rs" -o "$WORK_DIR/rust-bin" +else + echo "[skip] Rust benchmark: rustc not found" +fi + +echo +echo "Language | Median ms" +echo "---------|----------" + +run_many "Coderive" env COD_BENCHMARK_MODE=true java -cp "$WORK_DIR/coderive-java" cod.runner.CommandRunner \ + "$BENCH_DIR/coderive/CrossLanguageBenchmark.cod" --quiet | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' + +if [[ -f "$WORK_DIR/java-bin/CrossLanguageBenchmark.class" ]]; then + run_many "Java" java -cp "$WORK_DIR/java-bin" CrossLanguageBenchmark | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' +fi + +if [[ -x "$WORK_DIR/c-bin" ]]; then + run_many "C" "$WORK_DIR/c-bin" | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' +fi + +if [[ -x "$WORK_DIR/cpp-bin" ]]; then + run_many "C++" "$WORK_DIR/cpp-bin" | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' +fi + +if [[ -x "$WORK_DIR/rust-bin" ]]; then + run_many "Rust" "$WORK_DIR/rust-bin" | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' +fi + +if has_cmd python3; then + run_many "Python" python3 "$BENCH_DIR/python/cross_language_benchmark.py" | awk -F'|' '{printf "%-8s | %s\n", $1, $2}' +else + echo "[skip] Python benchmark: python3 not found" +fi diff --git a/benchmarks/rust/cross_language_benchmark.rs b/benchmarks/rust/cross_language_benchmark.rs new file mode 100644 index 00000000..8c44adc5 --- /dev/null +++ b/benchmarks/rust/cross_language_benchmark.rs @@ -0,0 +1,49 @@ +fn fib(n: i64) -> i128 { + if n == 0 { + return 0; + } + let mut a: i128 = 0; + let mut b: i128 = 1; + for _ in 1..=n { + let next = a + b; + a = b; + b = next; + } + a +} + +fn sum_squares(limit: i64) -> i128 { + let mut total: i128 = 0; + for i in 1..=limit { + let v = i as i128; + total += v * v; + } + total +} + +fn prime_count(limit: i64) -> i128 { + let mut count: i128 = 0; + for candidate in 2..=limit { + let mut is_prime = true; + if candidate > 2 { + for divisor in 2..candidate { + if candidate % divisor == 0 { + is_prime = false; + break; + } + } + } + if is_prime { + count += 1; + } + } + count +} + +fn main() { + let sum_part = sum_squares(2_000_000); + let fib_part = fib(35) * 2_000; + let prime_part = prime_count(5_000); + let checksum = sum_part + fib_part + prime_part; + println!("CHECKSUM:{}", checksum); +} diff --git a/src/bin/project.codc b/src/bin/project.codc new file mode 100644 index 00000000..57410bf6 Binary files /dev/null and b/src/bin/project.codc differ diff --git a/src/main/cod/demo/src/main/test/json/JsonStandardLibraryComprehensive.cod b/src/main/cod/demo/src/main/test/json/JsonStandardLibraryComprehensive.cod index c981f5e5..f6f509bc 100644 --- a/src/main/cod/demo/src/main/test/json/JsonStandardLibraryComprehensive.cod +++ b/src/main/cod/demo/src/main/test/json/JsonStandardLibraryComprehensive.cod @@ -52,7 +52,7 @@ share JsonStandardLibraryComprehensive { JsonStandardLibraryComprehensive.checkBool("array nested kind", mixedArray.get(4).isArray(), true) JsonStandardLibraryComprehensive.check("array nested value", mixedArray.get(4).get(1).asNumberText(), "3") - objectValue := Json.parse("{\"name\":\"Coderive\",\"ok\":true,\"n\":10,\"tags\":[\"lang\",\"json\"],\"meta\":{\"major\":0}}") + objectValue := Json.parse("\{\"name\":\"Coderive\",\"ok\":true,\"n\":10,\"tags\":[\"lang\",\"json\"],\"meta\":\{\"major\":0\}\}") JsonStandardLibraryComprehensive.checkBool("object kind", objectValue.isObject(), true) JsonStandardLibraryComprehensive.checkBool("object has name", objectValue.has("name"), true) JsonStandardLibraryComprehensive.check("object name", objectValue.getKey("name").asText(), "Coderive") @@ -61,7 +61,7 @@ share JsonStandardLibraryComprehensive { JsonStandardLibraryComprehensive.check("object nested object value", objectValue.getKey("meta").getKey("major").asNumberText(), "0") compact := Json.serialize(objectValue) - JsonStandardLibraryComprehensive.check("compact serialize", compact, "{\"name\":\"Coderive\",\"ok\":true,\"n\":10,\"tags\":[\"lang\",\"json\"],\"meta\":{\"major\":0}}") + JsonStandardLibraryComprehensive.check("compact serialize", compact, "\{\"name\":\"Coderive\",\"ok\":true,\"n\":10,\"tags\":[\"lang\",\"json\"],\"meta\":\{\"major\":0\}\}") pretty := Json.serializePretty(objectValue) JsonStandardLibraryComprehensive.checkBool("pretty has new lines", pretty.has("\n"), true) @@ -74,25 +74,25 @@ share JsonStandardLibraryComprehensive { versions.add(Json.numberText("0.9")) versions.add(Json.numberText("1.0")) created.set("versions", versions) - JsonStandardLibraryComprehensive.check("constructed serialize", Json.serialize(created), "{\"language\":\"Coderive\",\"stable\":false,\"versions\":[0.9,1.0]}") + JsonStandardLibraryComprehensive.check("constructed serialize", Json.serialize(created), "\{\"language\":\"Coderive\",\"stable\":false,\"versions\":[0.9,1.0]\}") updateObject := Json.object() updateObject.set("x", Json.numberText("1")) updateObject.set("x", Json.numberText("2")) - JsonStandardLibraryComprehensive.check("object key update", Json.serialize(updateObject), "{\"x\":2}") + JsonStandardLibraryComprehensive.check("object key update", Json.serialize(updateObject), "\{\"x\":2\}") - roundTripSource := " { \"a\" : [ 1 , 2 , {\"b\":false} ] , \"c\" : null } " + roundTripSource := " \{ \"a\" : [ 1 , 2 , \{\"b\":false\} ] , \"c\" : null \} " roundTrip := Json.parse(roundTripSource) JsonStandardLibraryComprehensive.checkBool("round trip parse ok", roundTrip.isError(), false) - JsonStandardLibraryComprehensive.check("round trip compact", Json.serialize(roundTrip), "{\"a\":[1,2,{\"b\":false}],\"c\":null}") + JsonStandardLibraryComprehensive.check("round trip compact", Json.serialize(roundTrip), "\{\"a\":[1,2,\{\"b\":false\}],\"c\":null\}") - invalid1 := Json.parse("{\"a\":1,}") + invalid1 := Json.parse("\{\"a\":1,\}") JsonStandardLibraryComprehensive.checkBool("invalid trailing comma object", invalid1.isError(), true) invalid2 := Json.parse("[1,2,]") JsonStandardLibraryComprehensive.checkBool("invalid trailing comma array", invalid2.isError(), true) - invalid3 := Json.parse("{\"a\" 1}") + invalid3 := Json.parse("\{\"a\" 1\}") JsonStandardLibraryComprehensive.checkBool("invalid missing colon", invalid3.isError(), true) invalid4 := Json.parse("\"unterminated") diff --git a/src/main/cod/std/json/Json.cod b/src/main/cod/std/json/Json.cod index c0c1c09c..65138505 100644 --- a/src/main/cod/std/json/Json.cod +++ b/src/main/cod/std/json/Json.cod @@ -12,7 +12,7 @@ share JsonValue { errorData: text = "" share this(kindValue: int) { - this.kind = kindValue + kind = kindValue } share isNull() :: bool { ~> (this.kind == 0) } @@ -37,10 +37,10 @@ share JsonValue { share add(item: JsonValue) { if this.kind != 4 { return } idx: int = this.arrayData.size - this.arrayData[idx] = item + arrayData[idx] = item } - share get(index: int) :: JsonValue { + share get(index: int) :: value: JsonValue { if this.kind != 4 { ~> (JsonValue.makeError("value is not an array")) } if index < 0 { ~> (JsonValue.makeError("array index out of bounds")) } if index >= this.arrayData.size { ~> (JsonValue.makeError("array index out of bounds")) } @@ -51,13 +51,13 @@ share JsonValue { if this.kind != 5 { return } for i of 0 to this.objectKeys.size - 1 { if this.objectKeys[i] == key { - this.objectValues[i] = value + objectValues[i] = value return } } idx: int = this.objectKeys.size - this.objectKeys[idx] = key - this.objectValues[idx] = value + objectKeys[idx] = key + objectValues[idx] = value } share has(key: text) :: bool { @@ -68,7 +68,7 @@ share JsonValue { ~> (false) } - share getKey(key: text) :: JsonValue { + share getKey(key: text) :: value: JsonValue { if this.kind != 5 { ~> (JsonValue.makeError("value is not an object")) } for i of 0 to this.objectKeys.size - 1 { if this.objectKeys[i] == key { @@ -85,7 +85,7 @@ share JsonValue { ~> (this.objectKeys[index]) } - share valueAt(index: int) :: JsonValue { + share valueAt(index: int) :: value: JsonValue { if this.kind != 5 { ~> (JsonValue.makeError("value is not an object")) } if index < 0 { ~> (JsonValue.makeError("object index out of bounds")) } if index >= this.objectValues.size { ~> (JsonValue.makeError("object index out of bounds")) } @@ -95,51 +95,58 @@ share JsonValue { share toJson() :: text { ~> (Json.serializeValue(this, false, 0)) } share toJsonPretty() :: text { ~> (Json.serializeValue(this, true, 0)) } - share makeNull() :: JsonValue { + share setBoolData(value: bool) { boolData = value } + share setTextData(value: text) { textData = value } + share setArrayData(value: []) { arrayData = value } + share setObjectKeys(value: [text]) { objectKeys = value } + share setObjectValues(value: []) { objectValues = value } + share setErrorData(value: text) { errorData = value } + + share makeNull() :: value: JsonValue { v := JsonValue(0) ~> (v) } - share makeBool(value: bool) :: JsonValue { + share makeBool(value: bool) :: value: JsonValue { v := JsonValue(1) - v.boolData = value + v.setBoolData(value) ~> (v) } - share makeNumber(textValue: text) :: JsonValue { + share makeNumber(textValue: text) :: value: JsonValue { v := JsonValue(2) - v.textData = textValue + v.setTextData(textValue) ~> (v) } - share makeText(textValue: text) :: JsonValue { + share makeText(textValue: text) :: value: JsonValue { v := JsonValue(3) - v.textData = textValue + v.setTextData(textValue) ~> (v) } - share makeArray(items: []) :: JsonValue { + share makeArray(items: []) :: value: JsonValue { v := JsonValue(4) - v.arrayData = items + v.setArrayData(items) ~> (v) } - share makeObject(keys: [text], values: []) :: JsonValue { + share makeObject(keys: [text], values: []) :: value: JsonValue { v := JsonValue(5) - v.objectKeys = keys - v.objectValues = values + v.setObjectKeys(keys) + v.setObjectValues(values) ~> (v) } - share makeError(message: text) :: JsonValue { + share makeError(message: text) :: value: JsonValue { v := JsonValue(6) - v.errorData = message + v.setErrorData(message) ~> (v) } } share Json { - share parse(source: text) :: JsonValue { + share parse(source: text) :: value: JsonValue { parser := JsonParser(source) ~> (parser.parseRoot()) } @@ -152,13 +159,13 @@ share Json { ~> (Json.serializeValue(value, true, 0)) } - share nullValue() :: JsonValue { ~> (JsonValue.makeNull()) } - share bool(value: bool) :: JsonValue { ~> (JsonValue.makeBool(value)) } - share number(value: int|float) :: JsonValue { ~> (JsonValue.makeNumber("" + value)) } - share numberText(value: text) :: JsonValue { ~> (JsonValue.makeNumber(value)) } - share text(value: text) :: JsonValue { ~> (JsonValue.makeText(value)) } - share array() :: JsonValue { ~> (JsonValue.makeArray([])) } - share object() :: JsonValue { ~> (JsonValue.makeObject([], [])) } + share nullValue() :: value: JsonValue { ~> (JsonValue.makeNull()) } + share bool(value: bool) :: value: JsonValue { ~> (JsonValue.makeBool(value)) } + share number(value: int|float) :: value: JsonValue { ~> (JsonValue.makeNumber("" + value)) } + share numberText(value: text) :: value: JsonValue { ~> (JsonValue.makeNumber(value)) } + share text(value: text) :: value: JsonValue { ~> (JsonValue.makeText(value)) } + share array() :: value: JsonValue { ~> (JsonValue.makeArray([])) } + share object() :: value: JsonValue { ~> (JsonValue.makeObject([], [])) } share serializeValue(value: JsonValue, pretty: bool, depth: int) :: text { if value.isError() { @@ -213,10 +220,10 @@ share Json { if value.isObject() { count := value.size() - if count == 0 { ~> ("{}") } + if count == 0 { ~> ("\{\}") } if pretty { - outText := "{\n" + outText := "\{\n" for i of 0 to count - 1 { key := value.keyAt(i) item := value.valueAt(i) @@ -226,11 +233,11 @@ share Json { if i < count - 1 { outText = outText + "," } outText = outText + "\n" } - outText = outText + Json.indent(depth) + "}" + outText = outText + Json.indent(depth) + "\}" ~> (outText) } - outText := "{" + outText := "\{" for i of 0 to count - 1 { key := value.keyAt(i) item := value.valueAt(i) @@ -238,7 +245,7 @@ share Json { outText = outText + Json.serializeValue(item, false, depth + 1) if i < count - 1 { outText = outText + "," } } - outText = outText + "}" + outText = outText + "\}" ~> (outText) } @@ -370,12 +377,12 @@ share Json { ~> (Json.hexDigit(d0) + Json.hexDigit(d1) + Json.hexDigit(d2) + Json.hexDigit(d3)) } - share isHighSurrogate(unit: int) :: bool { - ~> (all[unit >= 55296, unit <= 56319]) + share isHighSurrogate(codeUnit: int) :: bool { + ~> (all[codeUnit >= 55296, codeUnit <= 56319]) } - share isLowSurrogate(unit: int) :: bool { - ~> (all[unit >= 56320, unit <= 57343]) + share isLowSurrogate(codeUnit: int) :: bool { + ~> (all[codeUnit >= 56320, codeUnit <= 57343]) } share indent(depth: int) :: text { @@ -392,11 +399,11 @@ share JsonParser { index: int = 0 share this(sourceText: text) { - this.source = sourceText - this.index = 0 + source = sourceText + index = 0 } - share parseRoot() :: JsonValue { + share parseRoot() :: value: JsonValue { this.skipWhitespace() if this.index >= this.source.length { ~> (JsonValue.makeError("empty json input")) @@ -413,7 +420,7 @@ share JsonParser { ~> (value) } - share parseValue() :: JsonValue { + share parseValue() :: value: JsonValue { this.skipWhitespace() if this.index >= this.source.length { ~> (JsonValue.makeError("unexpected end of input")) @@ -426,42 +433,42 @@ share JsonParser { if ch == "f" { ~> (this.parseFalse()) } if ch == "\"" { ~> (this.parseText()) } if ch == "[" { ~> (this.parseArray()) } - if ch == "{" { ~> (this.parseObject()) } + if ch == "\{" { ~> (this.parseObject()) } if any[ch == "-", Json.isDigit(ch)] { ~> (this.parseNumber()) } ~> (JsonValue.makeError("invalid json value at index " + this.index)) } - share parseNull() :: JsonValue { + share parseNull() :: value: JsonValue { if this.matchKeyword("null") { - this.index = this.index + 4 + index = this.index + 4 ~> (JsonValue.makeNull()) } ~> (JsonValue.makeError("invalid token, expected null at index " + this.index)) } - share parseTrue() :: JsonValue { + share parseTrue() :: value: JsonValue { if this.matchKeyword("true") { - this.index = this.index + 4 + index = this.index + 4 ~> (JsonValue.makeBool(true)) } ~> (JsonValue.makeError("invalid token, expected true at index " + this.index)) } - share parseFalse() :: JsonValue { + share parseFalse() :: value: JsonValue { if this.matchKeyword("false") { - this.index = this.index + 5 + index = this.index + 5 ~> (JsonValue.makeBool(false)) } ~> (JsonValue.makeError("invalid token, expected false at index " + this.index)) } - share parseText() :: JsonValue { + share parseText() :: value: JsonValue { if this.source[this.index] != "\"" { ~> (JsonValue.makeError("expected text at index " + this.index)) } - this.index = this.index + 1 + index = this.index + 1 outText := "" for i of this.index to this.source.length { @@ -470,7 +477,7 @@ share JsonParser { } ch := this.source[this.index] - this.index = this.index + 1 + index = this.index + 1 if ch == "\"" { ~> (JsonValue.makeText(outText)) @@ -482,7 +489,7 @@ share JsonParser { } esc := this.source[this.index] - this.index = this.index + 1 + index = this.index + 1 if esc == "\"" { outText = outText + "\"" continue } if esc == "\\" { outText = outText + "\\" continue } @@ -498,7 +505,7 @@ share JsonParser { if firstUnit < 0 { ~> (JsonValue.makeError("invalid unicode escape at index " + this.index)) } - this.index = this.index + 4 + index = this.index + 4 firstText := "\\u" + Json.hex4(firstUnit) if Json.isHighSurrogate(firstUnit) { @@ -508,12 +515,12 @@ share JsonParser { if any[this.source[this.index] != "\\", this.source[this.index + 1] != "u"] { ~> (JsonValue.makeError("expected low surrogate escape at index " + this.index)) } - this.index = this.index + 2 + index = this.index + 2 secondUnit := this.parseHex4At(this.index) if any[secondUnit < 0, !Json.isLowSurrogate(secondUnit)] { ~> (JsonValue.makeError("invalid low surrogate at index " + this.index)) } - this.index = this.index + 4 + index = this.index + 4 outText = outText + firstText + "\\u" + Json.hex4(secondUnit) continue } @@ -535,18 +542,18 @@ share JsonParser { ~> (JsonValue.makeError("unterminated text")) } - share parseNumber() :: JsonValue { + share parseNumber() :: value: JsonValue { start := this.index if this.source[this.index] == "-" { - this.index = this.index + 1 + index = this.index + 1 if this.index >= this.source.length { ~> (JsonValue.makeError("invalid number at index " + start)) } } if this.source[this.index] == "0" { - this.index = this.index + 1 + index = this.index + 1 } else { if !Json.isDigit(this.source[this.index]) { ~> (JsonValue.makeError("invalid number at index " + start)) @@ -554,26 +561,26 @@ share JsonParser { for i of this.index to this.source.length { if this.index >= this.source.length { break } if !Json.isDigit(this.source[this.index]) { break } - this.index = this.index + 1 + index = this.index + 1 } } if all[this.index < this.source.length, this.source[this.index] == "."] { - this.index = this.index + 1 + index = this.index + 1 if any[this.index >= this.source.length, !Json.isDigit(this.source[this.index])] { ~> (JsonValue.makeError("invalid number fraction at index " + start)) } for i of this.index to this.source.length { if this.index >= this.source.length { break } if !Json.isDigit(this.source[this.index]) { break } - this.index = this.index + 1 + index = this.index + 1 } } if all[this.index < this.source.length, any[this.source[this.index] == "e", this.source[this.index] == "E"]] { - this.index = this.index + 1 + index = this.index + 1 if all[this.index < this.source.length, any[this.source[this.index] == "+", this.source[this.index] == "-"]] { - this.index = this.index + 1 + index = this.index + 1 } if any[this.index >= this.source.length, !Json.isDigit(this.source[this.index])] { ~> (JsonValue.makeError("invalid number exponent at index " + start)) @@ -581,7 +588,7 @@ share JsonParser { for i of this.index to this.source.length { if this.index >= this.source.length { break } if !Json.isDigit(this.source[this.index]) { break } - this.index = this.index + 1 + index = this.index + 1 } } @@ -589,17 +596,17 @@ share JsonParser { ~> (JsonValue.makeNumber(numberText)) } - share parseArray() :: JsonValue { + share parseArray() :: value: JsonValue { if this.source[this.index] != "[" { ~> (JsonValue.makeError("expected [ at index " + this.index)) } arr := JsonValue.makeArray([]) - this.index = this.index + 1 + index = this.index + 1 this.skipWhitespace() if all[this.index < this.source.length, this.source[this.index] == "]"] { - this.index = this.index + 1 + index = this.index + 1 ~> (arr) } @@ -615,7 +622,7 @@ share JsonParser { ch := this.source[this.index] if ch == "," { - this.index = this.index + 1 + index = this.index + 1 this.skipWhitespace() if all[this.index < this.source.length, this.source[this.index] == "]"] { ~> (JsonValue.makeError("trailing comma in array at index " + this.index)) @@ -624,7 +631,7 @@ share JsonParser { } if ch == "]" { - this.index = this.index + 1 + index = this.index + 1 ~> (arr) } @@ -634,17 +641,17 @@ share JsonParser { ~> (JsonValue.makeError("unterminated array")) } - share parseObject() :: JsonValue { - if this.source[this.index] != "{" { - ~> (JsonValue.makeError("expected { at index " + this.index)) + share parseObject() :: value: JsonValue { + if this.source[this.index] != "\{" { + ~> (JsonValue.makeError("expected \{ at index " + this.index)) } obj := JsonValue.makeObject([], []) - this.index = this.index + 1 + index = this.index + 1 this.skipWhitespace() - if all[this.index < this.source.length, this.source[this.index] == "}"] { - this.index = this.index + 1 + if all[this.index < this.source.length, this.source[this.index] == "\}"] { + index = this.index + 1 ~> (obj) } @@ -658,7 +665,7 @@ share JsonParser { if any[this.index >= this.source.length, this.source[this.index] != ":"] { ~> (JsonValue.makeError("expected : after object key at index " + this.index)) } - this.index = this.index + 1 + index = this.index + 1 value := this.parseValue() if value.isError() { ~> (value) } @@ -671,20 +678,20 @@ share JsonParser { ch := this.source[this.index] if ch == "," { - this.index = this.index + 1 + index = this.index + 1 this.skipWhitespace() - if all[this.index < this.source.length, this.source[this.index] == "}"] { + if all[this.index < this.source.length, this.source[this.index] == "\}"] { ~> (JsonValue.makeError("trailing comma in object at index " + this.index)) } continue } - if ch == "}" { - this.index = this.index + 1 + if ch == "\}" { + index = this.index + 1 ~> (obj) } - ~> (JsonValue.makeError("expected , or } in object at index " + this.index)) + ~> (JsonValue.makeError("expected , or \} in object at index " + this.index)) } ~> (JsonValue.makeError("unterminated object")) @@ -695,7 +702,7 @@ share JsonParser { if this.index >= this.source.length { return } ch := this.source[this.index] if any[ch == " ", ch == "\n", ch == "\r", ch == "\t"] { - this.index = this.index + 1 + index = this.index + 1 continue } return diff --git a/src/main/java/cod/ast/ASTFactory.java b/src/main/java/cod/ast/ASTFactory.java index a08e7364..85ee25a2 100644 --- a/src/main/java/cod/ast/ASTFactory.java +++ b/src/main/java/cod/ast/ASTFactory.java @@ -1,7 +1,7 @@ package cod.ast; import cod.ast.node.*; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; import cod.lexer.Token; import java.util.*; import cod.math.AutoStackingNumber; diff --git a/src/main/java/cod/ast/ASTPrinter.java b/src/main/java/cod/ast/ASTPrinter.java index d0757f21..29bb6149 100644 --- a/src/main/java/cod/ast/ASTPrinter.java +++ b/src/main/java/cod/ast/ASTPrinter.java @@ -1,6 +1,6 @@ package cod.ast; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Keyword.*; import cod.ast.node.*; public class ASTPrinter extends ASTVisitor { diff --git a/src/main/java/cod/ast/node/Field.java b/src/main/java/cod/ast/node/Field.java index fb062a93..a66fa3d6 100644 --- a/src/main/java/cod/ast/node/Field.java +++ b/src/main/java/cod/ast/node/Field.java @@ -1,7 +1,7 @@ package cod.ast.node; import cod.ast.VisitorImpl; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; public class Field extends Stmt { public String name; diff --git a/src/main/java/cod/ast/node/Method.java b/src/main/java/cod/ast/node/Method.java index 842b5dcb..0151c2bd 100644 --- a/src/main/java/cod/ast/node/Method.java +++ b/src/main/java/cod/ast/node/Method.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import cod.ast.VisitorImpl; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; public class Method extends Base { public String methodName; diff --git a/src/main/java/cod/ast/node/Policy.java b/src/main/java/cod/ast/node/Policy.java index 4e4a51d8..041dbd90 100644 --- a/src/main/java/cod/ast/node/Policy.java +++ b/src/main/java/cod/ast/node/Policy.java @@ -1,7 +1,7 @@ package cod.ast.node; import cod.ast.VisitorImpl; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/cod/ast/node/Type.java b/src/main/java/cod/ast/node/Type.java index 9d3557c3..10d106f4 100644 --- a/src/main/java/cod/ast/node/Type.java +++ b/src/main/java/cod/ast/node/Type.java @@ -7,7 +7,7 @@ import cod.ast.VisitorImpl; import cod.lexer.Token; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; public class Type extends Base { public String name; diff --git a/src/main/java/cod/debug/DebugSystem.java b/src/main/java/cod/debug/DebugSystem.java index d8518984..e59d41fa 100644 --- a/src/main/java/cod/debug/DebugSystem.java +++ b/src/main/java/cod/debug/DebugSystem.java @@ -30,6 +30,19 @@ public int getLevel() { private static boolean showThread = false; private static Map timers = new HashMap(); // Stores nanoseconds private static SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss.SSS"); + private static final boolean BENCHMARK_MODE = parseBenchmarkMode(); + + private static boolean parseBenchmarkMode() { + String raw = System.getProperty("cod.benchmark.mode"); + if (raw == null || raw.trim().isEmpty()) { + raw = System.getenv("COD_BENCHMARK_MODE"); + } + return raw != null && "true".equalsIgnoreCase(raw.trim()); + } + + public static boolean isBenchmarkMode() { + return BENCHMARK_MODE; + } public static void setLevel(Level level) { currentLevel = level; @@ -159,4 +172,4 @@ private static boolean shouldLog(Level level) { public static Level getLevel() { return currentLevel; } -} \ No newline at end of file +} diff --git a/src/main/java/cod/interpreter/Index.java b/src/main/java/cod/interpreter/Index.java index 8156aac6..b9a20bd1 100644 --- a/src/main/java/cod/interpreter/Index.java +++ b/src/main/java/cod/interpreter/Index.java @@ -1,11 +1,12 @@ package cod.interpreter; import cod.error.ProgramError; +import cod.debug.DebugSystem; import cod.ir.IRManager; import cod.lexer.*; import static cod.lexer.TokenType.*; -import static cod.syntax.Symbol.*; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; import java.io.*; import java.nio.charset.StandardCharsets; @@ -70,9 +71,13 @@ public static void setProjectRoot(String srcMainRoot) { } } - System.err.println("[INDEX] Project root set to: " + projectRoot); + if (!DebugSystem.isBenchmarkMode()) { + System.err.println("[INDEX] Project root set to: " + projectRoot); + } } catch (Exception e) { - System.err.println("[INDEX] Failed to set project root: " + e.getMessage()); + if (!DebugSystem.isBenchmarkMode()) { + System.err.println("[INDEX] Failed to set project root: " + e.getMessage()); + } } } diff --git a/src/main/java/cod/interpreter/Interpreter.java b/src/main/java/cod/interpreter/Interpreter.java index 7f31faf1..44c7b7f8 100644 --- a/src/main/java/cod/interpreter/Interpreter.java +++ b/src/main/java/cod/interpreter/Interpreter.java @@ -12,8 +12,8 @@ import cod.parser.MainParser; import cod.semantic.ImportResolver; import cod.semantic.ConstructorResolver; -import static cod.syntax.Keyword.*; -import static cod.syntax.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; import java.io.File; import java.io.BufferedReader; @@ -1087,7 +1087,7 @@ public Object evalMethodCall( argValue = typeSystem.normalizeForDeclaredType(paramType, argValue); - if (paramType.contains("|")) { + if (paramType != null && paramType.indexOf('|') >= 0) { String activeType = typeSystem.getConcreteType(typeSystem.unwrap(argValue)); argValue = new TypeHandler.Value(argValue, activeType, paramType); } diff --git a/src/main/java/cod/interpreter/InterpreterVisitor.java b/src/main/java/cod/interpreter/InterpreterVisitor.java index 75a86de1..3b865cbf 100644 --- a/src/main/java/cod/interpreter/InterpreterVisitor.java +++ b/src/main/java/cod/interpreter/InterpreterVisitor.java @@ -15,7 +15,7 @@ import cod.interpreter.exception.*; import cod.interpreter.handler.*; import java.util.*; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Keyword.*; import cod.semantic.ConstructorResolver; import cod.semantic.NamingValidator; @@ -416,7 +416,7 @@ public Object visit(Var node) { } } - if (resolvedDeclaredType.contains("|")) { + if (resolvedDeclaredType != null && resolvedDeclaredType.indexOf('|') >= 0) { String activeType = typeSystem.getConcreteType(typeSystem.unwrap(val)); val = new TypeHandler.Value(val, activeType, resolvedDeclaredType); ctx.setVariable(node.name, val); @@ -2148,7 +2148,7 @@ public Object visit(MethodCall node) { } } - if (paramType.contains("|")) { + if (paramType != null && paramType.indexOf('|') >= 0) { String activeType = typeSystem.getConcreteType(typeSystem.unwrap(argValue)); argValue = new TypeHandler.Value(argValue, activeType, paramType); } diff --git a/src/main/java/cod/interpreter/context/ExecutionContext.java b/src/main/java/cod/interpreter/context/ExecutionContext.java index 688cd9fd..a78180d8 100644 --- a/src/main/java/cod/interpreter/context/ExecutionContext.java +++ b/src/main/java/cod/interpreter/context/ExecutionContext.java @@ -391,6 +391,59 @@ public void setVariable(String name, Object value) { Object previous = currentScope.put(name, value); replaceTrackedValue(previous, value); } + + public int resolveVariableScopeIndex(String name) { + if (name == null) return -1; + for (int i = localsStack.size() - 1; i >= 0; i--) { + Map scope = localsStack.get(i); + if (scope.containsKey(name)) { + return i; + } + } + return localsStack.size() - 1; + } + + public int resolveVariableTypeScopeIndex(String name) { + if (name == null) return -1; + for (int i = localTypesStack.size() - 1; i >= 0; i--) { + Map scope = localTypesStack.get(i); + if (scope.containsKey(name)) { + return i; + } + } + return localTypesStack.size() - 1; + } + + public String getVariableTypeAtScope(int scopeIndex, String name) { + if (name == null || scopeIndex < 0 || scopeIndex >= localTypesStack.size()) { + return null; + } + return localTypesStack.get(scopeIndex).get(name); + } + + public void setVariableAtScope(int scopeIndex, String name, Object value) { + if (name == null) { + throw new InternalError("setVariableAtScope called with null name"); + } + if (scopeIndex < 0 || scopeIndex >= localsStack.size()) { + setVariable(name, value); + return; + } + Map scope = localsStack.get(scopeIndex); + Object previous = scope.put(name, value); + replaceTrackedValue(previous, value); + } + + public void setVariableTypeAtScope(int scopeIndex, String name, String type) { + if (name == null) { + throw new InternalError("setVariableTypeAtScope called with null name"); + } + if (scopeIndex < 0 || scopeIndex >= localTypesStack.size()) { + setVariableType(name, type); + return; + } + localTypesStack.get(scopeIndex).put(name, type); + } public String getVariableType(String name) { if (name == null) return null; diff --git a/src/main/java/cod/interpreter/handler/ArrayOperationHandler.java b/src/main/java/cod/interpreter/handler/ArrayOperationHandler.java index f3422763..ba21a8a1 100644 --- a/src/main/java/cod/interpreter/handler/ArrayOperationHandler.java +++ b/src/main/java/cod/interpreter/handler/ArrayOperationHandler.java @@ -15,6 +15,20 @@ import java.util.List; public class ArrayOperationHandler { + private static final class LoopVariableBinding { + final String name; + final int valueScopeIndex; + final int typeScopeIndex; + boolean typeInitialized; + + LoopVariableBinding(String name, int valueScopeIndex, int typeScopeIndex, boolean typeInitialized) { + this.name = name; + this.valueScopeIndex = valueScopeIndex; + this.typeScopeIndex = typeScopeIndex; + this.typeInitialized = typeInitialized; + } + } + private final InterpreterVisitor dispatcher; private final Interpreter interpreter; private final TypeHandler typeSystem; @@ -67,12 +81,13 @@ public Object executeForLoopNormally(For node) { public Object executeArrayLoop( cod.interpreter.context.ExecutionContext ctx, For node, String iter, Object arrayObj) { try { + LoopVariableBinding binding = resolveLoopVariableBinding(ctx, iter); if (arrayObj instanceof NaturalArray) { NaturalArray natural = (NaturalArray) arrayObj; long size = natural.size(); for (long i = 0; i < size; i++) { Object currentValue = natural.get(i); - ctx.setVariable(iter, currentValue); + writeLoopVariable(ctx, binding, currentValue); try { executeLoopBody(ctx, node); } catch (BreakLoopException e) { @@ -82,7 +97,7 @@ public Object executeArrayLoop( } else if (arrayObj instanceof List) { List list = (List) arrayObj; for (Object currentValue : list) { - ctx.setVariable(iter, currentValue); + writeLoopVariable(ctx, binding, currentValue); try { executeLoopBody(ctx, node); } catch (BreakLoopException e) { @@ -113,15 +128,17 @@ public Object executeRangeLoop(cod.interpreter.context.ExecutionContext ctx, For if (binOp.left instanceof Identifier && ((Identifier) binOp.left).name.equals(iter) && (binOp.op.equals("*") || binOp.op.equals("/"))) { + LoopVariableBinding loopBinding = resolveLoopVariableBinding(ctx, iter); Object rightObj = dispatcher.dispatch(binOp.right); rightObj = typeSystem.unwrap(rightObj); AutoStackingNumber factor = typeSystem.toAutoStackingNumber(rightObj); validateFactor(factor, binOp.op); - return executeMultiplicativeLoop(ctx, node, startObj, endObj, factor, binOp.op); + return executeMultiplicativeLoop(ctx, node, startObj, endObj, factor, binOp.op, loopBinding); } } AutoStackingNumber step; + LoopVariableBinding loopBinding = resolveLoopVariableBinding(ctx, iter); if (node.range.step != null) { Object stepObj = dispatcher.dispatch(node.range.step); step = typeSystem.toAutoStackingNumber(typeSystem.unwrap(stepObj)); @@ -135,7 +152,16 @@ public Object executeRangeLoop(cod.interpreter.context.ExecutionContext ctx, For throw new ProgramError("Loop step cannot be zero."); } - return executeAdditiveLoop(ctx, node, startObj, endObj, step); + Long startLong = tryFastLongValue(startObj); + Long endLong = tryFastLongValue(endObj); + Long stepLong = tryFastLongValue(step); + if (startLong != null && endLong != null && stepLong != null + && canUsePrimitiveAdditiveLoop(startLong.longValue(), endLong.longValue(), stepLong.longValue())) { + return executeAdditiveLoopPrimitive( + ctx, node, startLong.longValue(), endLong.longValue(), stepLong.longValue(), loopBinding); + } + + return executeAdditiveLoop(ctx, node, startObj, endObj, step, loopBinding); } catch (ProgramError e) { throw e; } catch (Exception e) { @@ -144,7 +170,12 @@ public Object executeRangeLoop(cod.interpreter.context.ExecutionContext ctx, For } public Object executeAdditiveLoop( - cod.interpreter.context.ExecutionContext ctx, For node, Object startObj, Object endObj, AutoStackingNumber step) { + cod.interpreter.context.ExecutionContext ctx, + For node, + Object startObj, + Object endObj, + AutoStackingNumber step, + LoopVariableBinding loopBinding) { try { AutoStackingNumber start = typeSystem.toAutoStackingNumber(startObj); AutoStackingNumber end = typeSystem.toAutoStackingNumber(endObj); @@ -153,7 +184,7 @@ public Object executeAdditiveLoop( while (shouldContinueAdditive(current, end, step, increasing)) { try { - executeIteration(ctx, node, current, startObj); + executeIteration(ctx, node, current, startObj, loopBinding); } catch (BreakLoopException e) { break; } @@ -173,7 +204,8 @@ public Object executeMultiplicativeLoop( Object startObj, Object endObj, AutoStackingNumber factor, - String operation) { + String operation, + LoopVariableBinding loopBinding) { try { AutoStackingNumber start = typeSystem.toAutoStackingNumber(startObj); AutoStackingNumber end = typeSystem.toAutoStackingNumber(endObj); @@ -181,7 +213,7 @@ public Object executeMultiplicativeLoop( while (shouldContinueMultiplicative(current, start, end, factor, operation)) { try { - executeIteration(ctx, node, current, startObj); + executeIteration(ctx, node, current, startObj, loopBinding); } catch (BreakLoopException e) { break; } @@ -200,16 +232,20 @@ public Object executeMultiplicativeLoop( } public void executeIteration( - cod.interpreter.context.ExecutionContext ctx, For node, AutoStackingNumber current, Object startObj) { + cod.interpreter.context.ExecutionContext ctx, + For node, + AutoStackingNumber current, + Object startObj, + LoopVariableBinding loopBinding) { try { - String iter = node.iterator; Object currentValue = convertToAppropriateType(current, startObj); - ctx.setVariable(iter, currentValue); - if (ctx.getVariableType(iter) == null) { + writeLoopVariable(ctx, loopBinding, currentValue); + if (!loopBinding.typeInitialized) { String inferredType = (current.fitsInStacks(1) && (current.getWords()[0] & 0x7FFFFFFFFFFFFFFFL) < Long.MAX_VALUE) - ? cod.syntax.Keyword.INT.toString() : cod.syntax.Keyword.FLOAT.toString(); - ctx.setVariableType(iter, inferredType); + ? cod.lexer.TokenType.Keyword.INT.toString() : cod.lexer.TokenType.Keyword.FLOAT.toString(); + ctx.setVariableTypeAtScope(loopBinding.typeScopeIndex, loopBinding.name, inferredType); + loopBinding.typeInitialized = true; } executeLoopBody(ctx, node); } catch (BreakLoopException e) { @@ -221,6 +257,80 @@ public void executeIteration( } } + public void executePrimitiveIteration( + cod.interpreter.context.ExecutionContext ctx, + For node, + long current, + LoopVariableBinding loopBinding) { + try { + Object currentValue = (current >= Integer.MIN_VALUE && current <= Integer.MAX_VALUE) + ? Integer.valueOf((int) current) + : Long.valueOf(current); + writeLoopVariable(ctx, loopBinding, currentValue); + if (!loopBinding.typeInitialized) { + ctx.setVariableTypeAtScope(loopBinding.typeScopeIndex, loopBinding.name, cod.lexer.TokenType.Keyword.INT.toString()); + loopBinding.typeInitialized = true; + } + executeLoopBody(ctx, node); + } catch (BreakLoopException e) { + throw e; + } catch (ProgramError e) { + throw e; + } catch (Exception e) { + throw new InternalError("Primitive loop iteration failed", e); + } + } + + public Object executeAdditiveLoopPrimitive( + cod.interpreter.context.ExecutionContext ctx, + For node, + long start, + long end, + long step, + LoopVariableBinding loopBinding) { + try { + long current = start; + if (step > 0L) { + while (current <= end) { + try { + executePrimitiveIteration(ctx, node, current, loopBinding); + } catch (BreakLoopException e) { + break; + } + if (current == end) { + break; + } + long next = current + step; + if (next < current) { + break; + } + current = next; + } + } else { + while (current >= end) { + try { + executePrimitiveIteration(ctx, node, current, loopBinding); + } catch (BreakLoopException e) { + break; + } + if (current == end) { + break; + } + long next = current + step; + if (next > current) { + break; + } + current = next; + } + } + return null; + } catch (ProgramError e) { + throw e; + } catch (Exception e) { + throw new InternalError("Primitive additive loop execution failed", e); + } + } + public void executeLoopBody(cod.interpreter.context.ExecutionContext ctx, For node) { try { for (Stmt s : node.body.statements) { @@ -547,6 +657,61 @@ private boolean shouldContinueAdditive( return increasing ? current.compareTo(end) <= 0 : current.compareTo(end) >= 0; } + private Long tryFastLongValue(Object value) { + Object unwrapped = typeSystem.unwrap(value); + if (unwrapped instanceof Integer || unwrapped instanceof Long + || unwrapped instanceof Short || unwrapped instanceof Byte) { + return Long.valueOf(((Number) unwrapped).longValue()); + } + if (unwrapped instanceof IntLiteral) { + try { + return Long.valueOf(((IntLiteral) unwrapped).value.longValue()); + } catch (ArithmeticException ignored) { + return null; + } + } + if (unwrapped instanceof AutoStackingNumber) { + AutoStackingNumber asn = (AutoStackingNumber) unwrapped; + try { + return Long.valueOf(asn.longValue()); + } catch (ArithmeticException ignored) { + return null; + } + } + return null; + } + + private boolean canUsePrimitiveAdditiveLoop(long start, long end, long step) { + if (step == 0L) { + return false; + } + // Long.MIN_VALUE is excluded because it cannot be safely negated (would overflow) + // and makes overflow-safe progression checks ambiguous. + if (step == Long.MIN_VALUE) { + return false; + } + if (step > 0L && start > end) { + return false; + } + if (step < 0L && start < end) { + return false; + } + return true; + } + + private LoopVariableBinding resolveLoopVariableBinding( + cod.interpreter.context.ExecutionContext ctx, String iter) { + int valueScopeIndex = ctx.resolveVariableScopeIndex(iter); + int typeScopeIndex = ctx.resolveVariableTypeScopeIndex(iter); + String existingType = ctx.getVariableTypeAtScope(typeScopeIndex, iter); + return new LoopVariableBinding(iter, valueScopeIndex, typeScopeIndex, existingType != null); + } + + private void writeLoopVariable( + cod.interpreter.context.ExecutionContext ctx, LoopVariableBinding binding, Object value) { + ctx.setVariableAtScope(binding.valueScopeIndex, binding.name, value); + } + private void validateFactor(AutoStackingNumber factor, String operation) { if (factor.compareTo(AutoStackingNumber.zero(1)) <= 0) { throw new ProgramError("Factor must be positive"); diff --git a/src/main/java/cod/interpreter/handler/AssignmentHandler.java b/src/main/java/cod/interpreter/handler/AssignmentHandler.java index c1ad5f25..1fbe1921 100644 --- a/src/main/java/cod/interpreter/handler/AssignmentHandler.java +++ b/src/main/java/cod/interpreter/handler/AssignmentHandler.java @@ -148,8 +148,9 @@ private Object assignToSlot(String slotTarget, Object value, ExecutionContext ct String declaredType = ctx.getSlotType(slotTarget); // O(1) validateAssignmentType(declaredType, value, slotTarget); value = typeSystem.normalizeForDeclaredType(declaredType, value); - - value = typeSystem.wrapUnionType(value, declaredType); + if (declaredType != null && declaredType.indexOf('|') >= 0) { + value = typeSystem.wrapUnionType(value, declaredType); + } ctx.setSlotValue(slotTarget, value); // O(1) ctx.markSlotAssigned(slotTarget); // O(1) @@ -438,7 +439,9 @@ private Object updateVariableInScope(String varName, Object newValue, if (declaredType != null) { validateAssignmentType(declaredType, newValue, varName); newValue = typeSystem.normalizeForDeclaredType(declaredType, newValue); - newValue = typeSystem.wrapUnionType(newValue, declaredType); + if (declaredType.indexOf('|') >= 0) { + newValue = typeSystem.wrapUnionType(newValue, declaredType); + } } ctx.setVariable(varName, newValue); diff --git a/src/main/java/cod/interpreter/handler/TypeHandler.java b/src/main/java/cod/interpreter/handler/TypeHandler.java index 346165ab..f97ac563 100644 --- a/src/main/java/cod/interpreter/handler/TypeHandler.java +++ b/src/main/java/cod/interpreter/handler/TypeHandler.java @@ -5,7 +5,7 @@ import cod.error.ProgramError; import cod.math.AutoStackingNumber; import cod.range.NaturalArray; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Keyword.*; import java.math.BigDecimal; import java.math.BigInteger; @@ -296,7 +296,7 @@ public boolean isValidForNullableType(String declaredType, Object value) { // === TypeHandler Conversion Helpers === public Object wrapUnionType(Object value, String declaredType) { - if (declaredType.contains("|")) { + if (declaredType != null && declaredType.indexOf('|') >= 0) { String activeType = getConcreteType(unwrap(value)); return new Value(value, activeType, declaredType); } @@ -443,7 +443,6 @@ public Object subtractNumbers(Object a, Object b) { } private Object subtractScalars(Object a, Object b) { - long[] fastPair = getFastLongPair(a, b); if (fastPair != null) { long av = fastPair[0]; @@ -1243,6 +1242,12 @@ public boolean validateType(String typeSig, Object value) { return true; } String typeSigTrimmed = normalizeTypeSignature(typeSig); + if (value != null && isFastPrimitiveSignature(typeSigTrimmed)) { + String concreteType = getConcreteType(value); + if (typeSigTrimmed.equals(concreteType)) { + return true; + } + } if (typeSigTrimmed.contains("|")) { if (!isTypeStructurallyValid(typeSigTrimmed)) { throw new ProgramError("Union type contains illegal keywords: " + typeSig); @@ -1255,6 +1260,16 @@ public boolean validateType(String typeSig, Object value) { String concreteType = getConcreteType(value); return validateTypeInternal(typeSig, value, concreteType); } + + private boolean isFastPrimitiveSignature(String typeSig) { + return INT.toString().equals(typeSig) + || FLOAT.toString().equals(typeSig) + || TEXT.toString().equals(typeSig) + || BOOL.toString().equals(typeSig) + || "none".equals(typeSig) + || TYPE.toString().equals(typeSig) + || "list".equals(typeSig); + } public boolean areEqual(Object a, Object b) { a = unwrap(a); diff --git a/src/main/java/cod/ir/DeserializationVisitor.java b/src/main/java/cod/ir/DeserializationVisitor.java index 8ac65cf5..40dbe69d 100644 --- a/src/main/java/cod/ir/DeserializationVisitor.java +++ b/src/main/java/cod/ir/DeserializationVisitor.java @@ -126,7 +126,7 @@ private static void applyNodeFields(Base node, Map values) { if (node instanceof Type) { Type n = (Type) node; if (values.containsKey("name")) n.name = (String) values.get("name"); - if (values.containsKey("visibility")) n.visibility = (cod.syntax.Keyword) values.get("visibility"); + if (values.containsKey("visibility")) n.visibility = (cod.lexer.TokenType.Keyword) values.get("visibility"); if (values.containsKey("extendName")) n.extendName = (String) values.get("extendName"); if (values.containsKey("fields")) n.fields = castList(values.get("fields")); if (values.containsKey("constructor")) n.constructor = (Constructor) values.get("constructor"); @@ -143,7 +143,7 @@ private static void applyNodeFields(Base node, Map values) { Field n = (Field) node; if (values.containsKey("name")) n.name = (String) values.get("name"); if (values.containsKey("type")) n.type = (String) values.get("type"); - if (values.containsKey("visibility")) n.visibility = (cod.syntax.Keyword) values.get("visibility"); + if (values.containsKey("visibility")) n.visibility = (cod.lexer.TokenType.Keyword) values.get("visibility"); if (values.containsKey("value")) n.value = (Expr) values.get("value"); return; } @@ -152,7 +152,7 @@ private static void applyNodeFields(Base node, Map values) { Method n = (Method) node; if (values.containsKey("methodName")) n.methodName = (String) values.get("methodName"); if (values.containsKey("associatedClass")) n.associatedClass = (String) values.get("associatedClass"); - if (values.containsKey("visibility")) n.visibility = (cod.syntax.Keyword) values.get("visibility"); + if (values.containsKey("visibility")) n.visibility = (cod.lexer.TokenType.Keyword) values.get("visibility"); if (values.containsKey("returnSlots")) n.returnSlots = castList(values.get("returnSlots")); if (values.containsKey("parameters")) n.parameters = castList(values.get("parameters")); if (values.containsKey("body")) n.body = castList(values.get("body")); @@ -192,7 +192,7 @@ private static void applyNodeFields(Base node, Map values) { if (node instanceof Policy) { Policy n = (Policy) node; if (values.containsKey("name")) n.name = (String) values.get("name"); - if (values.containsKey("visibility")) n.visibility = (cod.syntax.Keyword) values.get("visibility"); + if (values.containsKey("visibility")) n.visibility = (cod.lexer.TokenType.Keyword) values.get("visibility"); if (values.containsKey("methods")) n.methods = castList(values.get("methods")); if (values.containsKey("sourceUnit")) n.sourceUnit = (String) values.get("sourceUnit"); if (values.containsKey("composedPolicies")) n.composedPolicies = castList(values.get("composedPolicies")); diff --git a/src/main/java/cod/ir/IRCodec.java b/src/main/java/cod/ir/IRCodec.java index 0df07cc9..48892032 100644 --- a/src/main/java/cod/ir/IRCodec.java +++ b/src/main/java/cod/ir/IRCodec.java @@ -3,7 +3,7 @@ import cod.ast.node.*; import cod.math.AutoStackingNumber; import cod.parser.MainParser.ProgramType; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; import java.io.DataInput; import java.io.DataOutput; diff --git a/src/main/java/cod/lexer/IdentifierLexer.java b/src/main/java/cod/lexer/IdentifierLexer.java index 58fe9b4e..08be91ee 100644 --- a/src/main/java/cod/lexer/IdentifierLexer.java +++ b/src/main/java/cod/lexer/IdentifierLexer.java @@ -1,6 +1,6 @@ package cod.lexer; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; import cod.error.LexError; import java.util.*; diff --git a/src/main/java/cod/lexer/SymbolLexer.java b/src/main/java/cod/lexer/SymbolLexer.java index 252f5770..d1057a70 100644 --- a/src/main/java/cod/lexer/SymbolLexer.java +++ b/src/main/java/cod/lexer/SymbolLexer.java @@ -1,7 +1,7 @@ package cod.lexer; -import cod.syntax.Symbol; -import static cod.syntax.Symbol.*; +import cod.lexer.TokenType.Symbol; +import static cod.lexer.TokenType.Symbol.*; import java.util.*; public class SymbolLexer { diff --git a/src/main/java/cod/lexer/Token.java b/src/main/java/cod/lexer/Token.java index 168ebe4e..c255a128 100644 --- a/src/main/java/cod/lexer/Token.java +++ b/src/main/java/cod/lexer/Token.java @@ -1,7 +1,7 @@ package cod.lexer; -import cod.syntax.Symbol; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Symbol; +import cod.lexer.TokenType.Keyword; import java.util.List; import java.util.ArrayList; diff --git a/src/main/java/cod/lexer/TokenType.java b/src/main/java/cod/lexer/TokenType.java index b31a1e23..4f2e0b0c 100644 --- a/src/main/java/cod/lexer/TokenType.java +++ b/src/main/java/cod/lexer/TokenType.java @@ -1,5 +1,8 @@ package cod.lexer; +import java.util.HashMap; +import java.util.Map; + public enum TokenType { KEYWORD, INT_LIT, @@ -15,8 +18,115 @@ public enum TokenType { WS, INTERPOL; - @Override + private final String lowerCaseName = name().toLowerCase(); + + @Override public String toString() { - return name().toLowerCase(); + return lowerCaseName; + } + + public enum Keyword { + SHARE, + LOCAL, + UNIT, + USE, + IS, + THIS, + SUPER, + IF, + ELSE, + ELIF, + OF, + FOR, + BREAK, + SKIP, + TO, + BY, + INT, + TEXT, + FLOAT, + BOOL, + TYPE, + POLICY, + WITH, + BUILTIN, + ALL, + ANY, + EXIT, + NONE, + TRUE, + FALSE, + GET, + SET, + UNSAFE, + I8, + I16, + I32, + I64, + U8, + U16, + U32, + U64, + F32, + F64; + + private final String lowerCaseName = name().toLowerCase(); + + private static final Map STRING_TO_KEYWORD = new HashMap(); + + static { + for (Keyword keyword : values()) { + STRING_TO_KEYWORD.put(keyword.lowerCaseName, keyword); + } + } + + public static Keyword fromString(String text) { + return STRING_TO_KEYWORD.get(text.toLowerCase()); + } + + @Override + public String toString() { + return lowerCaseName; + } + } + + public enum Symbol { + EQ, + ASSIGN, + GT, + GTE, + LT, + LTE, + NEQ, + BANG, + PLUS, + PLUS_ASSIGN, + MINUS, + MINUS_ASSIGN, + MUL, + MUL_ASSIGN, + DIV, + DIV_ASSIGN, + LAMBDA, + MOD, + DOUBLE_COLON, + DOUBLE_COLON_ASSIGN, + TILDE_ARROW, + COLON, + DOT, + COMMA, + LPAREN, + RPAREN, + LBRACE, + RBRACE, + LBRACKET, + RBRACKET, + PIPE, + QUESTION, + AMPERSAND, + DOLLAR, + UNDERSCORE, + RANGE_DOTDOT, + RANGE_HASH } - } \ No newline at end of file +} diff --git a/src/main/java/cod/parser/BaseParser.java b/src/main/java/cod/parser/BaseParser.java index a55f53bf..dfaf3b48 100644 --- a/src/main/java/cod/parser/BaseParser.java +++ b/src/main/java/cod/parser/BaseParser.java @@ -6,10 +6,10 @@ import cod.lexer.TokenType; import static cod.lexer.TokenType.*; import cod.parser.context.*; -import cod.syntax.Keyword; -import static cod.syntax.Keyword.*; -import cod.syntax.Symbol; -import static cod.syntax.Symbol.*; +import cod.lexer.TokenType.Keyword; +import static cod.lexer.TokenType.Keyword.*; +import cod.lexer.TokenType.Symbol; +import static cod.lexer.TokenType.Symbol.*; import cod.semantic.ObjectValidator; import java.util.List; @@ -319,7 +319,7 @@ protected String parseQualifiedName() { } protected boolean canBeMethod(Token token) { - return is(token, OF, ALL, ANY); + return is(token, OF, ALL, ANY, GET, SET, INT, TEXT, FLOAT, BOOL, TYPE); } protected String parseTypeReference() { diff --git a/src/main/java/cod/parser/DeclarationParser.java b/src/main/java/cod/parser/DeclarationParser.java index e3749a81..2a676071 100644 --- a/src/main/java/cod/parser/DeclarationParser.java +++ b/src/main/java/cod/parser/DeclarationParser.java @@ -8,13 +8,13 @@ import cod.semantic.NamingValidator; import cod.semantic.PolicyValidator; import cod.semantic.ReturnContractValidator; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; import java.util.*; import cod.lexer.Token; import static cod.lexer.TokenType.*; -import static cod.syntax.Keyword.*; -import static cod.syntax.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; public class DeclarationParser extends BaseParser { diff --git a/src/main/java/cod/parser/ExpressionParser.java b/src/main/java/cod/parser/ExpressionParser.java index 56241a58..ee5043fa 100644 --- a/src/main/java/cod/parser/ExpressionParser.java +++ b/src/main/java/cod/parser/ExpressionParser.java @@ -9,8 +9,8 @@ import static cod.lexer.TokenType.*; import cod.math.AutoStackingNumber; import cod.parser.context.*; -import static cod.syntax.Symbol.*; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/cod/parser/MainParser.java b/src/main/java/cod/parser/MainParser.java index 49bf90bd..c4f8044d 100644 --- a/src/main/java/cod/parser/MainParser.java +++ b/src/main/java/cod/parser/MainParser.java @@ -11,8 +11,8 @@ import cod.lexer.Token; import static cod.lexer.TokenType.*; import cod.parser.context.*; -import static cod.syntax.Keyword.*; -import static cod.syntax.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; public class MainParser extends BaseParser { private static final String DEFAULT_UNIT_NAME = "default"; diff --git a/src/main/java/cod/parser/SlotParser.java b/src/main/java/cod/parser/SlotParser.java index 8ab3182f..7f898761 100644 --- a/src/main/java/cod/parser/SlotParser.java +++ b/src/main/java/cod/parser/SlotParser.java @@ -4,7 +4,7 @@ import cod.ast.node.*; import cod.lexer.Token; import static cod.lexer.TokenType.*; -import static cod.syntax.Symbol.*; +import static cod.lexer.TokenType.Symbol.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/cod/parser/StatementParser.java b/src/main/java/cod/parser/StatementParser.java index 7ed38132..6f2c1b13 100644 --- a/src/main/java/cod/parser/StatementParser.java +++ b/src/main/java/cod/parser/StatementParser.java @@ -12,8 +12,8 @@ import static cod.lexer.TokenType.*; import cod.parser.context.*; -import static cod.syntax.Symbol.*; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Symbol.*; +import static cod.lexer.TokenType.Keyword.*; public class StatementParser extends BaseParser { diff --git a/src/main/java/cod/parser/context/ParserContext.java b/src/main/java/cod/parser/context/ParserContext.java index b5a86ac4..14f9ed60 100644 --- a/src/main/java/cod/parser/context/ParserContext.java +++ b/src/main/java/cod/parser/context/ParserContext.java @@ -2,8 +2,9 @@ import cod.lexer.Token; import cod.lexer.TokenType; +import cod.lexer.TokenType.Keyword; +import cod.lexer.TokenType.Symbol; import cod.error.ParseError; -import cod.syntax.*; import static cod.semantic.ObjectValidator.is; import static cod.semantic.ObjectValidator.nil; diff --git a/src/main/java/cod/parser/context/TokenSkipper.java b/src/main/java/cod/parser/context/TokenSkipper.java index 205eba57..7890bd8a 100644 --- a/src/main/java/cod/parser/context/TokenSkipper.java +++ b/src/main/java/cod/parser/context/TokenSkipper.java @@ -4,10 +4,10 @@ import cod.lexer.TokenType; import static cod.lexer.TokenType.*; import cod.parser.context.*; -import cod.syntax.Keyword; -import static cod.syntax.Keyword.*; -import cod.syntax.Symbol; -import static cod.syntax.Symbol.*; +import cod.lexer.TokenType.Keyword; +import static cod.lexer.TokenType.Keyword.*; +import cod.lexer.TokenType.Symbol; +import static cod.lexer.TokenType.Symbol.*; import cod.semantic.ObjectValidator; import java.util.List; @@ -376,7 +376,7 @@ else if (braceDepth == 1) { } public boolean canBeMethod(Token t) { - return is(t, OF, ALL, ANY); + return is(t, OF, ALL, ANY, GET, SET, INT, TEXT, FLOAT, BOOL, TYPE); } public boolean isPolicyMethod() { diff --git a/src/main/java/cod/ptac/Executor.java b/src/main/java/cod/ptac/Executor.java index 2ad13b9a..50d10a79 100644 --- a/src/main/java/cod/ptac/Executor.java +++ b/src/main/java/cod/ptac/Executor.java @@ -5,16 +5,103 @@ import cod.ast.node.Program; import java.math.BigInteger; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; public final class Executor { private final Options options; private static final Object FALLBACK_SENTINEL = new Object(); + private static final class RuntimeState { + int fallbackCount; + final Map memory = new HashMap(); + final Map slots = new HashMap(); + } + + private static final class FastRegisterMap extends AbstractMap { + private final Map indexByName = new HashMap(); + private final Object[] values; + private final Map overflow = new HashMap(); + + FastRegisterMap(Function function) { + int index = 0; + if (function != null && function.parameters != null) { + for (String parameter : function.parameters) { + if (parameter != null && !indexByName.containsKey(parameter)) { + indexByName.put(parameter, Integer.valueOf(index++)); + } + } + } + if (function != null && function.instructions != null) { + for (Instruction instruction : function.instructions) { + if (instruction == null) continue; + if (instruction.dest != null && !indexByName.containsKey(instruction.dest)) { + indexByName.put(instruction.dest, Integer.valueOf(index++)); + } + if (instruction.operands == null) continue; + for (Operand operand : instruction.operands) { + if (operand == null) continue; + if (operand.kind == OperandKind.REGISTER && operand.value instanceof String) { + String name = (String) operand.value; + if (!indexByName.containsKey(name)) { + indexByName.put(name, Integer.valueOf(index++)); + } + } + } + } + } + this.values = new Object[index]; + } + + @Override + public Object get(Object key) { + if (!(key instanceof String)) return null; + String name = (String) key; + Integer index = indexByName.get(name); + if (index != null) { + return values[index.intValue()]; + } + return overflow.get(name); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof String)) return false; + String name = (String) key; + return indexByName.containsKey(name) || overflow.containsKey(name); + } + + @Override + public Object put(String key, Object value) { + if (key == null) return null; + Integer index = indexByName.get(key); + if (index != null) { + int idx = index.intValue(); + Object previous = values[idx]; + values[idx] = value; + return previous; + } + return overflow.put(key, value); + } + + @Override + public Set> entrySet() { + Set> entries = new HashSet>(); + for (Map.Entry item : indexByName.entrySet()) { + int idx = item.getValue().intValue(); + entries.add(new AbstractMap.SimpleEntry(item.getKey(), values[idx])); + } + entries.addAll(overflow.entrySet()); + return entries; + } + } + private static final class Range { final BigInteger start; final BigInteger end; @@ -65,16 +152,26 @@ public Object execute(Artifact artifact, Interpreter fallbackInterpreter) { if (artifact == null) { throw new ProgramError("Cannot execute null CodP-TAC artifact"); } + RuntimeState state = new RuntimeState(); if (artifact.unit == null || artifact.unit.functions == null || artifact.unit.functions.isEmpty()) { - return fallback(artifact, fallbackInterpreter, "No executable CodP-TAC unit in artifact"); + return fallback(artifact, fallbackInterpreter, "No executable CodP-TAC unit in artifact", state); } - Function entry = findEntry(artifact.unit); + Map functionIndex = indexFunctions(artifact.unit); + Function entry = findEntry(artifact.unit, functionIndex); if (entry == null) { - return fallback(artifact, fallbackInterpreter, "No entry function found in CodP-TAC unit"); - } - Object result = executeFunction(artifact.unit, entry, new ArrayList(), fallbackInterpreter, artifact); + return fallback(artifact, fallbackInterpreter, "No entry function found in CodP-TAC unit", state); + } + Object result = executeFunction( + artifact.unit, + entry, + new ArrayList(), + fallbackInterpreter, + artifact, + functionIndex, + state + ); return result == FALLBACK_SENTINEL ? null : result; } @@ -83,9 +180,11 @@ private Object executeFunction( Function function, List args, Interpreter fallbackInterpreter, - Artifact artifact + Artifact artifact, + Map functionIndex, + RuntimeState state ) { - Map registers = new HashMap(); + Map registers = new FastRegisterMap(function); if (function.parameters != null) { for (int i = 0; i < function.parameters.size(); i++) { Object arg = i < args.size() ? args.get(i) : null; @@ -103,7 +202,16 @@ private Object executeFunction( continue; } ExecutionResult result = runInstruction( - unit, inst, registers, fallbackInterpreter, artifact, labels, pc + unit, + function, + inst, + registers, + fallbackInterpreter, + artifact, + labels, + pc, + functionIndex, + state ); if (result.fallback) { return FALLBACK_SENTINEL; @@ -122,12 +230,15 @@ private Object executeFunction( private ExecutionResult runInstruction( Unit unit, + Function currentFunction, Instruction inst, Map registers, Interpreter fallbackInterpreter, Artifact artifact, Map labels, - int currentPc + int currentPc, + Map functionIndex, + RuntimeState state ) { if (inst.opcode == Opcode.ASSIGN) { Object value = operandValue(inst.operands, 0, registers); @@ -162,7 +273,8 @@ private ExecutionResult runInstruction( Object fallback = fallback( artifact, fallbackInterpreter, - "Non-numeric range bounds are not yet natively executed" + "Non-numeric range bounds are not yet natively executed", + state ); if (fallback == FALLBACK_SENTINEL) return ExecutionResult.fallback(); } @@ -193,7 +305,8 @@ private ExecutionResult runInstruction( Object fallback = fallback( artifact, fallbackInterpreter, - "Unknown branch label: " + label + "Unknown branch label: " + label, + state ); if (fallback == FALLBACK_SENTINEL) return ExecutionResult.fallback(); } @@ -209,7 +322,8 @@ private ExecutionResult runInstruction( Object fallback = fallback( artifact, fallbackInterpreter, - "Unknown branch-if label: " + label + "Unknown branch-if label: " + label, + state ); if (fallback == FALLBACK_SENTINEL) return ExecutionResult.fallback(); } @@ -249,7 +363,15 @@ private ExecutionResult runInstruction( } if (inst.opcode == Opcode.MAP) { - Object mapped = runMap(unit, inst, registers, fallbackInterpreter, artifact); + Object mapped = runMap( + unit, + inst, + registers, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (mapped == FALLBACK_SENTINEL) { return ExecutionResult.fallback(); } @@ -258,7 +380,15 @@ private ExecutionResult runInstruction( } if (inst.opcode == Opcode.FILTER) { - Object filtered = runFilter(unit, inst, registers, fallbackInterpreter, artifact); + Object filtered = runFilter( + unit, + inst, + registers, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (filtered == FALLBACK_SENTINEL) { return ExecutionResult.fallback(); } @@ -267,7 +397,15 @@ private ExecutionResult runInstruction( } if (inst.opcode == Opcode.REDUCE) { - Object reduced = runReduce(unit, inst, registers, fallbackInterpreter, artifact); + Object reduced = runReduce( + unit, + inst, + registers, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (reduced == FALLBACK_SENTINEL) { return ExecutionResult.fallback(); } @@ -276,7 +414,15 @@ private ExecutionResult runInstruction( } if (inst.opcode == Opcode.FILTER_MAP) { - Object filteredMapped = runFilterMap(unit, inst, registers, fallbackInterpreter, artifact); + Object filteredMapped = runFilterMap( + unit, + inst, + registers, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (filteredMapped == FALLBACK_SENTINEL) { return ExecutionResult.fallback(); } @@ -284,17 +430,66 @@ private ExecutionResult runInstruction( return ExecutionResult.normal(filteredMapped); } + if (inst.opcode == Opcode.SLOT_SET) { + String slotName = String.valueOf(operandValue(inst.operands, 0, registers)); + Object value = operandValue(inst.operands, 1, registers); + state.slots.put(slotName, value); + if (inst.dest != null) registers.put(inst.dest, value); + return ExecutionResult.normal(value); + } + + if (inst.opcode == Opcode.SLOT_GET) { + String slotName = String.valueOf(operandValue(inst.operands, 0, registers)); + Object value = state.slots.get(slotName); + if (inst.dest != null) registers.put(inst.dest, value); + return ExecutionResult.normal(value); + } + + if (inst.opcode == Opcode.SLOT_UNPACK) { + Object source = operandValue(inst.operands, 0, registers); + if (source instanceof Map) { + @SuppressWarnings("unchecked") + Map sourceMap = (Map) source; + for (Map.Entry entry : sourceMap.entrySet()) { + if (entry.getKey() instanceof String) { + String key = (String) entry.getKey(); + state.slots.put(key, entry.getValue()); + registers.put(key, entry.getValue()); + } + } + } + return ExecutionResult.normal(source); + } + + if (inst.opcode == Opcode.SLOT_RET) { + return ExecutionResult.returned(new HashMap(state.slots)); + } + + if (inst.opcode == Opcode.SLOT_DIV) { + return ExecutionResult.normal(null); + } + + if (inst.opcode == Opcode.STORE) { + Object address = operandValue(inst.operands, 0, registers); + Object value = operandValue(inst.operands, 1, registers); + state.memory.put(address, value); + if (inst.dest != null) registers.put(inst.dest, value); + return ExecutionResult.normal(value); + } + + if (inst.opcode == Opcode.LOAD) { + Object address = operandValue(inst.operands, 0, registers); + Object value = state.memory.get(address); + if (inst.dest != null) registers.put(inst.dest, value); + return ExecutionResult.normal(value); + } + if (inst.opcode == Opcode.FILTER || inst.opcode == Opcode.SCAN || inst.opcode == Opcode.ZIP || inst.opcode == Opcode.WHERE || inst.opcode == Opcode.FILTER_MAP_REDUCE || inst.opcode == Opcode.LAZY_SLICE - || inst.opcode == Opcode.SLOT_GET - || inst.opcode == Opcode.SLOT_SET - || inst.opcode == Opcode.SLOT_RET - || inst.opcode == Opcode.SLOT_UNPACK - || inst.opcode == Opcode.SLOT_DIV || inst.opcode == Opcode.ANCESTOR || inst.opcode == Opcode.SELF || inst.opcode == Opcode.TAIL_CALL @@ -302,25 +497,41 @@ private ExecutionResult runInstruction( || inst.opcode == Opcode.FORMULA_SEQ || inst.opcode == Opcode.FORMULA_COND || inst.opcode == Opcode.FORMULA_RECUR - || inst.opcode == Opcode.FORMULA_FUSE - || inst.opcode == Opcode.STORE - || inst.opcode == Opcode.LOAD) { - Object fallback = fallback(artifact, fallbackInterpreter, "Opcode not yet natively executed: " + inst.opcode); + || inst.opcode == Opcode.FORMULA_FUSE) { + Object fallback = fallback( + artifact, + fallbackInterpreter, + "Opcode not yet natively executed: " + inst.opcode, + state + ); if (fallback == FALLBACK_SENTINEL) return ExecutionResult.fallback(); } if (inst.opcode == Opcode.CALL) { String functionName = String.valueOf(operandValue(inst.operands, 0, registers)); - Function target = findFunction(unit, functionName); + Function target = findFunction(functionIndex, functionName); if (target == null) { - Object fallback = fallback(artifact, fallbackInterpreter, "Unknown function: " + functionName); + Object fallback = fallback( + artifact, + fallbackInterpreter, + "Unknown function: " + functionName, + state + ); if (fallback == FALLBACK_SENTINEL) return ExecutionResult.fallback(); } List args = new ArrayList(); for (int i = 1; i < inst.operands.size(); i++) { args.add(operandValue(inst.operands, i, registers)); } - Object result = executeFunction(unit, target, args, fallbackInterpreter, artifact); + Object result = executeFunction( + unit, + target, + args, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (result == FALLBACK_SENTINEL) { return ExecutionResult.fallback(); } @@ -335,7 +546,15 @@ private ExecutionResult runInstruction( return ExecutionResult.normal(null); } - private Object fallback(Artifact artifact, Interpreter fallbackInterpreter, String reason) { + private Object fallback( + Artifact artifact, + Interpreter fallbackInterpreter, + String reason, + RuntimeState state + ) { + if (state != null) { + state.fallbackCount++; + } if (!options.isFallbackEnabled()) { throw new ProgramError("CodP-TAC execution failed without fallback: " + reason); } @@ -353,20 +572,29 @@ private Object fallback(Artifact artifact, Interpreter fallbackInterpreter, Stri throw new ProgramError("CodP-TAC fallback unavailable: " + reason); } - private Function findEntry(Unit unit) { + private Function findEntry(Unit unit, Map functionIndex) { if (unit.entryFunction != null) { - Function explicit = findFunction(unit, unit.entryFunction); + Function explicit = findFunction(functionIndex, unit.entryFunction); if (explicit != null) return explicit; } - return findFunction(unit, "main"); + return findFunction(functionIndex, "main"); } - private Function findFunction(Unit unit, String name) { - if (unit == null || unit.functions == null || name == null) return null; - for (Function fn : unit.functions) { - if (fn != null && name.equals(fn.name)) return fn; + private Map indexFunctions(Unit unit) { + if (unit == null || unit.functions == null || unit.functions.isEmpty()) { + return Collections.emptyMap(); } - return null; + Map functions = new HashMap(); + for (Function function : unit.functions) { + if (function == null || function.name == null) continue; + functions.put(function.name, function); + } + return functions; + } + + private Function findFunction(Map functionIndex, String name) { + if (functionIndex == null || functionIndex.isEmpty() || name == null) return null; + return functionIndex.get(name); } private Object operandValue(List operands, int index, Map registers) { @@ -416,6 +644,53 @@ private boolean isCompare(Opcode opcode) { } private Object evaluateMath(Opcode opcode, Object a, Object b) { + if (isFloatingLike(a) || isFloatingLike(b)) { + double left = toDouble(a); + double right = toDouble(b); + if (opcode == Opcode.ADD) return Double.valueOf(left + right); + if (opcode == Opcode.SUB) return Double.valueOf(left - right); + if (opcode == Opcode.MUL) return Double.valueOf(left * right); + if (opcode == Opcode.DIV) { + if (right == 0.0d) { + throw new ProgramError("CodP-TAC division by zero"); + } + return Double.valueOf(left / right); + } + if (opcode == Opcode.MOD) { + if (right == 0.0d) { + throw new ProgramError("CodP-TAC modulo by zero"); + } + return Double.valueOf(left % right); + } + } + + if (isLongLike(a) && isLongLike(b)) { + long left = toLong(a); + long right = toLong(b); + if (opcode == Opcode.ADD && !willOverflowAdd(left, right)) { + return Long.valueOf(left + right); + } + if (opcode == Opcode.SUB && !willOverflowSub(left, right)) { + return Long.valueOf(left - right); + } + if (opcode == Opcode.MUL && !willOverflowMul(left, right)) { + return Long.valueOf(left * right); + } + if (opcode == Opcode.DIV) { + if (right == 0L) { + throw new ProgramError("CodP-TAC division by zero"); + } + return Long.valueOf(left / right); + } + if (opcode == Opcode.MOD) { + if (right == 0L) { + throw new ProgramError("CodP-TAC modulo by zero"); + } + long modulus = right < 0L ? -right : right; + return Long.valueOf(left % modulus); + } + } + BigInteger left = toBigInt(a); BigInteger right = toBigInt(b); if (opcode == Opcode.ADD) return left.add(right); @@ -437,6 +712,30 @@ private Object evaluateMath(Opcode opcode, Object a, Object b) { } private Boolean evaluateCompare(Opcode opcode, Object a, Object b) { + if (isFloatingLike(a) || isFloatingLike(b)) { + double left = toDouble(a); + double right = toDouble(b); + int cmp = left < right ? -1 : (left > right ? 1 : 0); + if (opcode == Opcode.EQ) return cmp == 0; + if (opcode == Opcode.NE) return cmp != 0; + if (opcode == Opcode.GT) return cmp > 0; + if (opcode == Opcode.LT) return cmp < 0; + if (opcode == Opcode.GTE) return cmp >= 0; + if (opcode == Opcode.LTE) return cmp <= 0; + return false; + } + if (isLongLike(a) && isLongLike(b)) { + long left = toLong(a); + long right = toLong(b); + int cmp = left < right ? -1 : (left > right ? 1 : 0); + if (opcode == Opcode.EQ) return cmp == 0; + if (opcode == Opcode.NE) return cmp != 0; + if (opcode == Opcode.GT) return cmp > 0; + if (opcode == Opcode.LT) return cmp < 0; + if (opcode == Opcode.GTE) return cmp >= 0; + if (opcode == Opcode.LTE) return cmp <= 0; + return false; + } BigInteger left = toBigInt(a); BigInteger right = toBigInt(b); int cmp = left.compareTo(right); @@ -488,23 +787,33 @@ private Object runMap( Instruction inst, Map registers, Interpreter fallbackInterpreter, - Artifact artifact + Artifact artifact, + Map functionIndex, + RuntimeState state ) { List source = asSequence(operandValue(inst.operands, 0, registers)); if (source == null) { - return fallback(artifact, fallbackInterpreter, "MAP source is not a sequence"); + return fallback(artifact, fallbackInterpreter, "MAP source is not a sequence", state); } String mapperName = String.valueOf(operandValue(inst.operands, 1, registers)); - Function mapper = findFunction(unit, mapperName); + Function mapper = findFunction(functionIndex, mapperName); if (mapper == null) { - return fallback(artifact, fallbackInterpreter, "MAP function not found: " + mapperName); + return fallback(artifact, fallbackInterpreter, "MAP function not found: " + mapperName, state); } List out = new ArrayList(source.size()); for (int i = 0; i < source.size(); i++) { Object element = source.get(i); List args = new ArrayList(); args.add(element); - Object mapped = executeFunction(unit, mapper, args, fallbackInterpreter, artifact); + Object mapped = executeFunction( + unit, + mapper, + args, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (mapped == FALLBACK_SENTINEL) { return FALLBACK_SENTINEL; } @@ -518,23 +827,38 @@ private Object runFilter( Instruction inst, Map registers, Interpreter fallbackInterpreter, - Artifact artifact + Artifact artifact, + Map functionIndex, + RuntimeState state ) { List source = asSequence(operandValue(inst.operands, 0, registers)); if (source == null) { - return fallback(artifact, fallbackInterpreter, "FILTER source is not a sequence"); + return fallback(artifact, fallbackInterpreter, "FILTER source is not a sequence", state); } String predicateName = String.valueOf(operandValue(inst.operands, 1, registers)); - Function predicate = findFunction(unit, predicateName); + Function predicate = findFunction(functionIndex, predicateName); if (predicate == null) { - return fallback(artifact, fallbackInterpreter, "FILTER function not found: " + predicateName); + return fallback( + artifact, + fallbackInterpreter, + "FILTER function not found: " + predicateName, + state + ); } List out = new ArrayList(); for (int i = 0; i < source.size(); i++) { Object element = source.get(i); List args = new ArrayList(); args.add(element); - Object keep = executeFunction(unit, predicate, args, fallbackInterpreter, artifact); + Object keep = executeFunction( + unit, + predicate, + args, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (keep == FALLBACK_SENTINEL) { return FALLBACK_SENTINEL; } @@ -550,26 +874,41 @@ private Object runReduce( Instruction inst, Map registers, Interpreter fallbackInterpreter, - Artifact artifact + Artifact artifact, + Map functionIndex, + RuntimeState state ) { List source = asSequence(operandValue(inst.operands, 0, registers)); if (source == null) { - return fallback(artifact, fallbackInterpreter, "REDUCE source is not a sequence"); + return fallback(artifact, fallbackInterpreter, "REDUCE source is not a sequence", state); } if (source.isEmpty()) { return null; } String reducerName = String.valueOf(operandValue(inst.operands, 1, registers)); - Function reducer = findFunction(unit, reducerName); + Function reducer = findFunction(functionIndex, reducerName); if (reducer == null) { - return fallback(artifact, fallbackInterpreter, "REDUCE function not found: " + reducerName); + return fallback( + artifact, + fallbackInterpreter, + "REDUCE function not found: " + reducerName, + state + ); } Object accumulator = source.get(0); for (int i = 1; i < source.size(); i++) { List args = new ArrayList(); args.add(accumulator); args.add(source.get(i)); - Object reduced = executeFunction(unit, reducer, args, fallbackInterpreter, artifact); + Object reduced = executeFunction( + unit, + reducer, + args, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (reduced == FALLBACK_SENTINEL) { return FALLBACK_SENTINEL; } @@ -583,21 +922,24 @@ private Object runFilterMap( Instruction inst, Map registers, Interpreter fallbackInterpreter, - Artifact artifact + Artifact artifact, + Map functionIndex, + RuntimeState state ) { List source = asSequence(operandValue(inst.operands, 0, registers)); if (source == null) { - return fallback(artifact, fallbackInterpreter, "FILTER_MAP source is not a sequence"); + return fallback(artifact, fallbackInterpreter, "FILTER_MAP source is not a sequence", state); } String predicateName = String.valueOf(operandValue(inst.operands, 1, registers)); String mapperName = String.valueOf(operandValue(inst.operands, 2, registers)); - Function predicate = findFunction(unit, predicateName); - Function mapper = findFunction(unit, mapperName); + Function predicate = findFunction(functionIndex, predicateName); + Function mapper = findFunction(functionIndex, mapperName); if (predicate == null || mapper == null) { return fallback( artifact, fallbackInterpreter, - "FILTER_MAP function not found: predicate=" + predicateName + ", mapper=" + mapperName + "FILTER_MAP function not found: predicate=" + predicateName + ", mapper=" + mapperName, + state ); } List out = new ArrayList(); @@ -605,14 +947,30 @@ private Object runFilterMap( Object element = source.get(i); List predicateArgs = new ArrayList(); predicateArgs.add(element); - Object keep = executeFunction(unit, predicate, predicateArgs, fallbackInterpreter, artifact); + Object keep = executeFunction( + unit, + predicate, + predicateArgs, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (keep == FALLBACK_SENTINEL) { return FALLBACK_SENTINEL; } if (isTruthy(keep)) { List mapperArgs = new ArrayList(); mapperArgs.add(element); - Object mapped = executeFunction(unit, mapper, mapperArgs, fallbackInterpreter, artifact); + Object mapped = executeFunction( + unit, + mapper, + mapperArgs, + fallbackInterpreter, + artifact, + functionIndex, + state + ); if (mapped == FALLBACK_SENTINEL) { return FALLBACK_SENTINEL; } @@ -749,4 +1107,61 @@ private BigInteger toBigInt(Object value) { return BigInteger.ZERO; } } + + private boolean isFloatingLike(Object value) { + if (value instanceof Float || value instanceof Double) return true; + if (value instanceof Number) return false; + if (value == null) return false; + String text = String.valueOf(value).trim(); + return text.indexOf('.') >= 0 || text.indexOf('e') >= 0 || text.indexOf('E') >= 0; + } + + private boolean isLongLike(Object value) { + if (value == null) return false; + if (value instanceof Float || value instanceof Double) return false; + if (value instanceof Number || value instanceof BigInteger) return true; + try { + Long.parseLong(String.valueOf(value).trim()); + return true; + } catch (Exception ignored) { + return false; + } + } + + private long toLong(Object value) { + if (value == null) return 0L; + if (value instanceof Number) return ((Number) value).longValue(); + if (value instanceof BigInteger) return ((BigInteger) value).longValue(); + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (Exception ignored) { + return 0L; + } + } + + private double toDouble(Object value) { + if (value == null) return 0.0d; + if (value instanceof Number) return ((Number) value).doubleValue(); + try { + return Double.parseDouble(String.valueOf(value).trim()); + } catch (Exception ignored) { + return 0.0d; + } + } + + private boolean willOverflowAdd(long a, long b) { + return (b > 0L && a > Long.MAX_VALUE - b) || (b < 0L && a < Long.MIN_VALUE - b); + } + + private boolean willOverflowSub(long a, long b) { + return (b < 0L && a > Long.MAX_VALUE + b) || (b > 0L && a < Long.MIN_VALUE + b); + } + + private boolean willOverflowMul(long a, long b) { + if (a == 0L || b == 0L) return false; + if (a == Long.MIN_VALUE && b == -1L) return true; + if (b == Long.MIN_VALUE && a == -1L) return true; + long result = a * b; + return result / b != a; + } } diff --git a/src/main/java/cod/ptac/Options.java b/src/main/java/cod/ptac/Options.java index 1f479bbb..ab41e4aa 100644 --- a/src/main/java/cod/ptac/Options.java +++ b/src/main/java/cod/ptac/Options.java @@ -51,14 +51,14 @@ public boolean isFallbackEnabled() { } private static Mode parseMode(String raw) { - if (raw == null) return Mode.INTERPRETER; + if (raw == null) return Mode.COMPILE_EXECUTE; String normalized = raw.trim().toLowerCase(); if ("interpreter".equals(normalized)) return Mode.INTERPRETER; - if ("compile-only".equals(normalized)) return Mode.COMPILE_ONLY; + if ("compile-only".equals(normalized) || "compile_only".equals(normalized)) return Mode.COMPILE_ONLY; if ("compile_execute".equals(normalized) || "compile-execute".equals(normalized)) { return Mode.COMPILE_EXECUTE; } - return Mode.INTERPRETER; + return Mode.COMPILE_EXECUTE; } private static String firstNonEmpty(String a, String b) { diff --git a/src/main/java/cod/runner/BaseRunner.java b/src/main/java/cod/runner/BaseRunner.java index 49c4db1a..3b9e6e5f 100644 --- a/src/main/java/cod/runner/BaseRunner.java +++ b/src/main/java/cod/runner/BaseRunner.java @@ -96,8 +96,10 @@ public Program parse(String filename, Interpreter interpreter) throws Exception protected void configureDebugSystem(DebugSystem.Level level) { DebugSystem.setLevel(level); - DebugSystem.setShowTimestamp(true); - DebugSystem.info(LOG_TAG, "DebugSystem configured to level: " + level); + DebugSystem.setShowTimestamp(!DebugSystem.isBenchmarkMode()); + if (!DebugSystem.isBenchmarkMode()) { + DebugSystem.info(LOG_TAG, "DebugSystem configured to level: " + level); + } } protected String extractFilenameFromArgs(String[] args, String defaultFilename) { diff --git a/src/main/java/cod/runner/CommandRunner.java b/src/main/java/cod/runner/CommandRunner.java index d82ddff4..a72db633 100644 --- a/src/main/java/cod/runner/CommandRunner.java +++ b/src/main/java/cod/runner/CommandRunner.java @@ -36,6 +36,7 @@ public void run(String[] args) throws Exception { } String outputFilename = null; + boolean forceInterpreter = false; RunnerConfig config = processArgs( @@ -52,7 +53,7 @@ public void configure(RunnerConfig config) { for (int i = 0; i < args.length; i++) { String arg = args[i]; if ("--interpret".equals(arg) || "-i".equals(arg)) { - // Default mode, do nothing + forceInterpreter = true; } else if ("-o".equals(arg)) { if (i + 1 < args.length) { outputFilename = args[i + 1]; @@ -102,12 +103,15 @@ public void configure(RunnerConfig config) { // Initialize IR manager initializeIRManager(); - executeInterpretation(ast); + executeInterpretation(ast, forceInterpreter); DebugSystem.info(NAME + LOG_TAG, "CommandRunner execution completed"); } finally { - System.out.println("\n-----------------------------"); - System.out.println("Execution completed! Duration: " + DebugSystem.stopTimer("exec") + "ms"); + double duration = DebugSystem.stopTimer("exec"); + if (!DebugSystem.isBenchmarkMode()) { + System.out.println("\n-----------------------------"); + System.out.println("Execution completed! Duration: " + duration + "ms"); + } } } @@ -253,7 +257,7 @@ private void initializeIRManager() { } } - private void executeInterpretation(Program ast) { + private void executeInterpretation(Program ast, boolean forceInterpreter) { DebugSystem.info(NAME + LOG_TAG, "Starting program interpretation"); boolean hasImports = @@ -275,7 +279,11 @@ private void executeInterpretation(Program ast) { DebugSystem.debug(NAME + LOG_TAG, "Skipping index/IR generation (no imports)"); } - if (ptacOptions.isCompileExecuteEnabled() && irManager != null && ast != null && ast.unit != null) { + if (!forceInterpreter + && ptacOptions.isCompileExecuteEnabled() + && irManager != null + && ast != null + && ast.unit != null) { Type entryType = findMainType(ast); if (entryType != null) { Artifact artifact = irManager.loadArtifact(ast.unit.name, entryType.name); @@ -332,7 +340,7 @@ private void printHelp() { out(" CommandRunner compile [-f|--full]"); out(); out("Options:"); - out(" -i, --interpret Interpret the program (default)"); + out(" -i, --interpret Force AST interpreter execution"); out(" -o Write output to file"); out(" --debug Enable debug output"); out(" --trace Enable trace-level debugging"); @@ -343,7 +351,7 @@ private void printHelp() { out(" compile Compile source to bytecode container (.codc with .codb entries)"); out(" -f, --full Full compile all .cod files under src/main"); out("Environment flags:"); - out(" COD_PTAC_MODE=interpreter|compile-only|compile-execute"); + out(" COD_PTAC_MODE=interpreter|compile-only|compile-execute (default: compile-execute)"); out(" COD_PTAC_FALLBACK=true|false"); out(); out("Examples:"); diff --git a/src/main/java/cod/runner/REPLRunner.java b/src/main/java/cod/runner/REPLRunner.java index 8c00fda9..e62fc19d 100644 --- a/src/main/java/cod/runner/REPLRunner.java +++ b/src/main/java/cod/runner/REPLRunner.java @@ -18,7 +18,7 @@ import java.util.Map; import java.util.Scanner; -import cod.syntax.Keyword; +import cod.lexer.TokenType.Keyword; public class REPLRunner { diff --git a/src/main/java/cod/semantic/ConstructorResolver.java b/src/main/java/cod/semantic/ConstructorResolver.java index ab94a92d..630d5c3a 100644 --- a/src/main/java/cod/semantic/ConstructorResolver.java +++ b/src/main/java/cod/semantic/ConstructorResolver.java @@ -8,7 +8,7 @@ import cod.interpreter.*; import cod.interpreter.context.*; import cod.interpreter.handler.*; -import static cod.syntax.Keyword.*; +import static cod.lexer.TokenType.Keyword.*; import java.util.*; diff --git a/src/main/java/cod/semantic/NamingValidator.java b/src/main/java/cod/semantic/NamingValidator.java index f14a3d9a..b9db2f73 100644 --- a/src/main/java/cod/semantic/NamingValidator.java +++ b/src/main/java/cod/semantic/NamingValidator.java @@ -128,6 +128,18 @@ public static boolean startsWithLowerCase(String name) { } public static boolean isAllCaps(String name) { - return name != null && name.matches("[A-Z0-9_]+"); + if (name == null || name.isEmpty()) { + return false; + } + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + boolean ok = (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_'; + if (!ok) { + return false; + } + } + return true; } } diff --git a/src/main/java/cod/semantic/ObjectValidator.java b/src/main/java/cod/semantic/ObjectValidator.java index 6510e7e2..9f5cd970 100644 --- a/src/main/java/cod/semantic/ObjectValidator.java +++ b/src/main/java/cod/semantic/ObjectValidator.java @@ -1,7 +1,8 @@ package cod.semantic; import cod.lexer.*; -import cod.syntax.*; +import cod.lexer.TokenType.Keyword; +import cod.lexer.TokenType.Symbol; public final class ObjectValidator { private ObjectValidator() {} @@ -57,4 +58,4 @@ public static boolean nil(Object... objects) { } return false; } -} \ No newline at end of file +} diff --git a/src/main/java/cod/syntax/Keyword.java b/src/main/java/cod/syntax/Keyword.java deleted file mode 100644 index fb1f297f..00000000 --- a/src/main/java/cod/syntax/Keyword.java +++ /dev/null @@ -1,100 +0,0 @@ -package cod.syntax; - -import java.util.HashMap; -import java.util.Map; - -/** - * This enum contains all of the keywords for the Coderive language. - */ -public enum Keyword { - // Visibility modifiers - SHARE, - LOCAL, - - // namespace - UNIT, - - // import - USE, - - // extends & instanceof - IS, - - THIS, - SUPER, - - IF, - ELSE, - ELIF, - - OF, - - // Loop - FOR, - BREAK, - SKIP, /* Equivalent of CONTINUE */ - - // Used in loop and natural arrays - TO, - BY, - - // Primitive types - INT, - TEXT, - FLOAT, - BOOL, - - // metaprimitive type - TYPE, - - // for policy classes declaration. - POLICY, - WITH, - - BUILTIN, - - ALL, - ANY, - - EXIT, - - NONE, - TRUE, - FALSE, - - // For more control and auto wrappings of variables - GET, - SET, - - // Toggle strictly after visibility modifier to be able to use unsafe primitives and do low level operations - UNSAFE, - - // Unsafe primitives (for low-level operations) - I8, - I16, - I32, - I64, - U8, - U16, - U32, - U64, - F32, - F64; - - private static final Map STRING_TO_KEYWORD = new HashMap<>(); - - static { - for (Keyword keyword : values()) { - STRING_TO_KEYWORD.put(keyword.toString(), keyword); - } - } - - public static Keyword fromString(String text) { - return STRING_TO_KEYWORD.get(text.toLowerCase()); - } - - @Override - public String toString() { - return name().toLowerCase(); - } -} diff --git a/src/main/java/cod/syntax/Symbol.java b/src/main/java/cod/syntax/Symbol.java deleted file mode 100644 index 4bf6b07b..00000000 --- a/src/main/java/cod/syntax/Symbol.java +++ /dev/null @@ -1,46 +0,0 @@ -package cod.syntax; - -public enum Symbol { - EQ, - ASSIGN, - GT, - GTE, - LT, - LTE, - NEQ, - BANG, - - PLUS, - PLUS_ASSIGN, - MINUS, - MINUS_ASSIGN, - MUL, - MUL_ASSIGN, - DIV, - DIV_ASSIGN, - - LAMBDA, - - MOD, - DOUBLE_COLON, - DOUBLE_COLON_ASSIGN, - TILDE_ARROW, - COLON, - DOT, - COMMA, - LPAREN, - RPAREN, - LBRACE, - RBRACE, - LBRACKET, - RBRACKET, - - PIPE, - QUESTION, - AMPERSAND, - DOLLAR, - UNDERSCORE, - - RANGE_DOTDOT, // ".." for range - RANGE_HASH // "#" for step -}