- Add String.fromChar function to convert Char to String - Create four stress test projects demonstrating Lux features: - json-parser: recursive descent parsing with Char handling - markdown-converter: string manipulation and ADTs - todo-app: list operations and pattern matching - mini-interpreter: AST evaluation and environments - Add comprehensive testing documentation (docs/testing.md) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
227 lines
7.2 KiB
Plaintext
227 lines
7.2 KiB
Plaintext
// JSON Parser - Stress tests recursion, ADTs, and string manipulation
|
|
//
|
|
// This demonstrates:
|
|
// - Recursive descent parsing
|
|
// - Complex ADTs for AST representation
|
|
// - Character handling with Char type
|
|
// - Error handling with Result type
|
|
|
|
// JSON Value representation
|
|
type JsonValue =
|
|
| JsonNull
|
|
| JsonBool(Bool)
|
|
| JsonNumber(Int)
|
|
| JsonString(String)
|
|
| JsonArray(List<JsonValue>)
|
|
|
|
// Parser state: input chars and position
|
|
type ParseState =
|
|
| ParseState(List<Char>, Int)
|
|
|
|
// Parser result
|
|
type ParseResult<T> =
|
|
| ParseOk(T, ParseState)
|
|
| ParseErr(String)
|
|
|
|
// Get current character
|
|
fn peek(state: ParseState): Option<Char> =
|
|
match state {
|
|
ParseState(chars, pos) => List.get(chars, pos)
|
|
}
|
|
|
|
// Advance position
|
|
fn advance(state: ParseState): ParseState =
|
|
match state {
|
|
ParseState(chars, pos) => ParseState(chars, pos + 1)
|
|
}
|
|
|
|
// Skip whitespace
|
|
fn skipWhitespace(state: ParseState): ParseState =
|
|
match peek(state) {
|
|
None => state,
|
|
Some(c) =>
|
|
if c == ' ' || c == '\t' || c == '\n' then
|
|
skipWhitespace(advance(state))
|
|
else state
|
|
}
|
|
|
|
// Check if character is a digit
|
|
fn isDigit(c: Char): Bool =
|
|
c == '0' || c == '1' || c == '2' || c == '3' || c == '4' ||
|
|
c == '5' || c == '6' || c == '7' || c == '8' || c == '9'
|
|
|
|
fn digitValue(c: Char): Int =
|
|
if c == '0' then 0
|
|
else if c == '1' then 1
|
|
else if c == '2' then 2
|
|
else if c == '3' then 3
|
|
else if c == '4' then 4
|
|
else if c == '5' then 5
|
|
else if c == '6' then 6
|
|
else if c == '7' then 7
|
|
else if c == '8' then 8
|
|
else if c == '9' then 9
|
|
else 0
|
|
|
|
// Parse a number
|
|
fn parseNumber(state: ParseState): ParseResult<Int> =
|
|
parseDigits(state, 0, false)
|
|
|
|
fn parseDigits(state: ParseState, acc: Int, hasDigits: Bool): ParseResult<Int> =
|
|
match peek(state) {
|
|
None =>
|
|
if hasDigits then ParseOk(acc, state)
|
|
else ParseErr("Expected digit"),
|
|
Some(c) =>
|
|
if isDigit(c) then
|
|
parseDigits(advance(state), acc * 10 + digitValue(c), true)
|
|
else if hasDigits then ParseOk(acc, state)
|
|
else ParseErr("Expected digit")
|
|
}
|
|
|
|
// Parse a string (simple version - no escapes)
|
|
fn parseString(state: ParseState): ParseResult<String> =
|
|
match peek(state) {
|
|
None => ParseErr("Unexpected end"),
|
|
Some(c) =>
|
|
if c != '"' then ParseErr("Expected quote")
|
|
else parseStringContent(advance(state), "")
|
|
}
|
|
|
|
fn charToString(c: Char): String =
|
|
String.fromChar(c)
|
|
|
|
fn parseStringContent(state: ParseState, acc: String): ParseResult<String> =
|
|
match peek(state) {
|
|
None => ParseErr("Unterminated string"),
|
|
Some(c) =>
|
|
if c == '"' then ParseOk(acc, advance(state))
|
|
else parseStringContent(advance(state), acc + charToString(c))
|
|
}
|
|
|
|
// Check if next chars match a keyword
|
|
fn matchKeyword(state: ParseState, keyword: String): Bool =
|
|
matchKeywordChars(state, String.chars(keyword), 0)
|
|
|
|
fn matchKeywordChars(state: ParseState, keywordChars: List<Char>, idx: Int): Bool =
|
|
match List.get(keywordChars, idx) {
|
|
None => true,
|
|
Some(kc) =>
|
|
match state {
|
|
ParseState(chars, pos) =>
|
|
match List.get(chars, pos + idx) {
|
|
None => false,
|
|
Some(ic) => if ic == kc then matchKeywordChars(state, keywordChars, idx + 1) else false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn advanceBy(state: ParseState, n: Int): ParseState =
|
|
if n <= 0 then state
|
|
else advanceBy(advance(state), n - 1)
|
|
|
|
// Main value parser
|
|
fn parseValue(state: ParseState): ParseResult<JsonValue> = {
|
|
let s = skipWhitespace(state)
|
|
match peek(s) {
|
|
None => ParseErr("Unexpected end of input"),
|
|
Some(c) =>
|
|
if matchKeyword(s, "null") then
|
|
ParseOk(JsonNull, advanceBy(s, 4))
|
|
else if matchKeyword(s, "true") then
|
|
ParseOk(JsonBool(true), advanceBy(s, 4))
|
|
else if matchKeyword(s, "false") then
|
|
ParseOk(JsonBool(false), advanceBy(s, 5))
|
|
else if c == '"' then
|
|
match parseString(s) {
|
|
ParseErr(e) => ParseErr(e),
|
|
ParseOk(str, newState) => ParseOk(JsonString(str), newState)
|
|
}
|
|
else if c == '[' then parseArray(s)
|
|
else if isDigit(c) then
|
|
match parseNumber(s) {
|
|
ParseErr(e) => ParseErr(e),
|
|
ParseOk(num, newState) => ParseOk(JsonNumber(num), newState)
|
|
}
|
|
else ParseErr("Unexpected character")
|
|
}
|
|
}
|
|
|
|
// Parse array
|
|
fn parseArray(state: ParseState): ParseResult<JsonValue> =
|
|
match peek(state) {
|
|
None => ParseErr("Unexpected end"),
|
|
Some(c) =>
|
|
if c != '[' then ParseErr("Expected [")
|
|
else parseArrayElements(skipWhitespace(advance(state)), [])
|
|
}
|
|
|
|
fn parseArrayElements(state: ParseState, acc: List<JsonValue>): ParseResult<JsonValue> =
|
|
match peek(state) {
|
|
None => ParseErr("Unterminated array"),
|
|
Some(c) =>
|
|
if c == ']' then ParseOk(JsonArray(acc), advance(state))
|
|
else match parseValue(state) {
|
|
ParseErr(e) => ParseErr(e),
|
|
ParseOk(value, newState) => {
|
|
let afterComma = skipWhitespace(newState)
|
|
match peek(afterComma) {
|
|
None => ParseErr("Unterminated array"),
|
|
Some(next) =>
|
|
if next == ']' then ParseOk(JsonArray(List.concat(acc, [value])), advance(afterComma))
|
|
else if next == ',' then parseArrayElements(skipWhitespace(advance(afterComma)), List.concat(acc, [value]))
|
|
else ParseErr("Expected , or ]")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public parse function
|
|
fn parse(input: String): Result<JsonValue, String> =
|
|
match parseValue(ParseState(String.chars(input), 0)) {
|
|
ParseErr(e) => Err(e),
|
|
ParseOk(value, _) => Ok(value)
|
|
}
|
|
|
|
// Pretty print JSON
|
|
fn valueToString(value: JsonValue): String =
|
|
match value {
|
|
JsonNull => "null",
|
|
JsonBool(b) => if b then "true" else "false",
|
|
JsonNumber(n) => toString(n),
|
|
JsonString(s) => "\"" + s + "\"",
|
|
JsonArray(items) => "[" + String.join(List.map(items, valueToString), ", ") + "]"
|
|
}
|
|
|
|
// Test the parser
|
|
fn main(): Unit with {Console} = {
|
|
Console.print("=== JSON Parser Test ===")
|
|
Console.print("")
|
|
|
|
Console.print("Test 1: Simple values")
|
|
testParse("null")
|
|
testParse("true")
|
|
testParse("false")
|
|
testParse("42")
|
|
testParse("\"hello\"")
|
|
Console.print("")
|
|
|
|
Console.print("Test 2: Arrays")
|
|
testParse("[]")
|
|
testParse("[1, 2, 3]")
|
|
testParse("[true, false, null]")
|
|
testParse("[1, [2, 3], 4]")
|
|
Console.print("")
|
|
|
|
Console.print("Test 3: Nested arrays")
|
|
testParse("[[1, 2], [3, 4]]")
|
|
}
|
|
|
|
fn testParse(input: String): Unit with {Console} =
|
|
match parse(input) {
|
|
Ok(value) => Console.print(" " + input + " => " + valueToString(value)),
|
|
Err(e) => Console.print(" ERROR: " + e)
|
|
}
|
|
|
|
let output = run main() with {}
|