app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.4/6XA8JLWhmG1FSgb8GxPjBs9cGrtRqopohR3KAHYo722z.tar.zst" } import pf.Stdout # import pf.Stderr import pf.File main! : List(Str) => Try({}, [Exit(I32)]) main! = |args| { match args { [_, file_name] => { text = File.read_utf8!(file_name) ? |e| { Stderr.line!("Failed to read `${file_name}`:\n${Inspect.to_str(e)}") Exit(1) } utf8 = Str.to_utf8(text) parsed = parse(utf8, 0, []) ? |e| { Stderr.line!("Failed to parse:\n${e}") Exit(1) } typed = type_check(parsed) ? |e| { Stderr.line!("Failed to type check:\n${e}") Exit(1) } js = emit_js(typed) js_file = "${file_name}.js" File.write_utf8!(js_file, js) ? |e| { Stderr.line!("Failed to write js to `${js_file}`:\n${Inspect.to_str(e)}") Exit(1) } Ok({}) } _ => { Stdout.line!("Invalid arguments: expected just the name of a file to compile and execute") Err(Exit(1)) } } } type_check = |ast| { Ok(ast) } emit_js = |_typed_ast| {"TODO"} TokenContents : [ NewlineToken, OpenBracketToken, # ( CloseBracketToken, # ) OpenSquareBracketToken, # [ CloseSquareBracketToken, # ] OpenBraceToken, # { CloseBraceToken, # } CommaToken, SymbolsToken(Str), DigitsToken(Str), CamalCaseIdentToken(Str), SnakeCaseIdentToken(Str), CommentToken(Str), StringToken(Str), EndOfFileToken, ] Token : (TokenContents, U64) tokenize_identifier = |file, index, acc, start_index| { char = List.get(file, index) ret = || { match Str.from_utf8(acc) { Ok(str) => { (Ok(SnakeCaseIdentToken(str)), start_index, index) } Err(BadUtf8({problem, index: i})) => { (Err("Invalid UTF8 at index ${Num.to_str(i)}: ${Inspect.to_str(problem)}"), start_index, index) } } } match char { Ok(c) => { if ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z') or (c == '_') { tokenize_identifier(file, index+1, List.append(acc, c), start_index) } else { ret() } } _ => ret() } } TokenizerResult : ( Try(TokenContents, Str), U64, # Index of start of token/error U64, # New index in file ) get_next_token : List(U8), U64 -> TokenizerResult get_next_token = |file, index| { match List.get(file, index) { Ok('\n') => { (Ok(NewlineToken), index, index + 1) } Ok(c) => { if ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z') or (c == '_') { tokenize_identifier(file, index+1, [c], index) } else { match Str.from_utf8([c]) { Ok(s) => (Err("Unexpected character `${s}`"), index, index + 1) Err(e) => (Err(Inspect.to_str(e)), index, index + 1) } } } Err(_) => { (Ok(EndOfFileToken), index, index) } } } #parse_pattern_match_starting_with_snake_case_ident : List(U8), Str, TokenizerResult -> Try(_, Str) parse_pattern_match_starting_with_snake_case_ident = |file, ident, tokenizer_result| { (token, _, index) = tokenizer_result match token { Ok(SymbolsToken(":")) => { (type, index2) = parse_type(file, index)? (token3, token3_pos, index3) = get_next_token(file, index2) Ok((Definition(ident, Some(type)), token3, index3, [])) } _ => { Ok((Definition(ident, None), token, index, ["A `:` to specify the type of the `${ident}` variable"])) } } } parse_pattern_match = |file, tokenizer_result| { (token, _, index) = tokenizer_result match token { Ok(SnakeCaseIdentToken(ident)) => { parse_pattern_match_starting_with_snake_case_ident(file, ident, get_next_token(file, index)) } #Ok((CamalCaseIdentToken ident)) => {} _ => { Err(format_error(tokenizer_result, ["A snake case identifier for the name of a variable being created by a pattern match"])) } } } #format_error : TokenizerResult, List(Str) -> Str format_error = |tokenizer_output, possibilities| { (result, index, _) = tokenizer_output got = match result { Ok(token) => { match token { CamalCaseIdentToken(s) => "The camal case ident `${s}`" SnakeCaseIdentToken(s) => "The snake case ident `${s}`" CommaToken => "A comma" _ => "TODO: Implement token to string" } } Err(msg) => "the tokenization error `${msg}`" } expected = match possibilities { [one] => ": ${one}" _ => { expected_str = possibilities.map(|e| "- ${e}")->Str.join_with("\n") " either:\n${expected_str}" } } "At index ${Num.to_str(index)}:\nExpected${expected}\nGot: ${got}" } # Comment : { text : Str, position : U64 } # Match : { # value : Value, # branches : List {}, # TODO # } # Loop : { # captures : List Str, # in : Value, # block : List Statement, # } # # Maybe(T) : [ # Some T, # None, # ] # # ValueDestination : [ # # TODO: Add pattern matching # Mutation (Str, Maybe Type), # Definition (Str, Maybe Type), # infer if it is a constant or mutable definition from wether there is an underscore at the end of the variable name # ] # Operation : [ SetTo, IncrementBy, DecrementBy, MultiplyBy, DivideBy, ] # Statement : [ # VariableStatement({ dest : ValueDestination, op : Operation, value : Value }), # CaseStatement({ value: Value, branches : List((ValueDestination, List(Statement)))}), ] # FuncValue : { arguments : List(Str), body : List(Statement) } Value : [ FunctionCall, FunctionValue(FuncValue), VariableReference(Str), ] # Type : [ Name(Str), SumType(List((Str, Type))), StructType(List((Str, Type, Value))), ] # ValueDefinition(possibleValues) : { name : Str, value : possibleValues, type : Type, position : U64 } # TopLevel : [ Import({ name : Str, position : U64 }), ValueDefinition({name: Str, position: U64, value: FuncValue}), TypeDefinition({name: Str, type: Type, position: U64}), ] parse_type = |file, index| { match get_next_token(file, index) { (Ok(CamalCaseIdentToken(type)), _, index2) => Ok((Name(type), index2)) got => Err(format_error(got, ["a type"])) } } parse_value = |file, tokenizer_result, possibilities| { (token, token_pos, index) = tokenizer_result match token { Ok(SnakeCaseIdentToken(n)) => { match get_next_token(file, index) { (Ok(OpenBracketToken), _, newIndex) => Ok((FunctionCall({}), newIndex)) _ => Ok((VariableReference(n), index)) } } # TODO _ => Err(format_error((token, token_pos, index), List.concat(possibilities, ["An identifier for a variable to be referenced"]))) } } parse_function_call_args = |file, index, acc| { (token, tokenPos, index2) = get_next_token(file, index) match token { Ok(CloseBracketToken) => Ok((acc, index2)) _ => { (value, index3) = parse_value(file, (token, tokenPos, index2), ["A close bracket token"])? (token2, token2_pos, index4) = get_next_token(file, index3) match token2 { Ok(CommaToken) => parse_function_call_args(file, index4, List.append(acc, value)) Ok(CloseBracketToken) => Ok((acc, index4)) _ => Err(format_error((token2, token2_pos, index4), ["`,`", "`)`"])) } } } } parse_case_branches = |file, index, acc| { (token, tokenPos, index2) = get_next_token(file, index) match token { # TODO Ok(CloseBraceToken) => Ok((acc, index2)) _ => Err(format_error((token, tokenPos, index2), ["A close brace token to end the case statement"])) } } parse_block = |file, index, acc| { match get_next_token(file, index) { (Ok(SnakeCaseIdentToken("case")), _, index2) => { (value, index3) = parse_value(file, get_next_token(file, index2), [])? index4 = ( match get_next_token(file, index3) { (Ok(OpenBraceToken), _, i) => Ok(i) got => Err(format_error(got, ["`{` to start the block of the case statement"])) } )? (branches, index5) = parse_case_branches(file, index4, [])? parse_block(file, index5, List.append(acc, CaseStatement({value, branches}))) } (Ok(SnakeCaseIdentToken(n)), _, index2) => { (token, token_pos, index3) = get_next_token(file, index2) match token { Ok(OpenBracketToken) => { # TODO: Parse function call (args, index4) = parse_function_call_args(file, index3, [])? parse_block(file, index4, acc) } _ => { # TODO: Parse variable assignment (pattern, token4, index4, possibilities) = parse_pattern_match_starting_with_snake_case_ident(file, n, (token, token_pos, index3))? parse_block(file, index4, acc) } } } (Ok(CloseBraceToken), _, newIndex) => Ok((acc, newIndex)) got => Err(format_error(got, ["An identifier", "}"])) } } parse_function_def_args = |file, index, acc| { match get_next_token(file, index) { (Ok(SnakeCaseIdentToken(name)), _, newIndex) => { match get_next_token(file, newIndex) { (Ok(CommaToken), _, newNewIndex) => parse_function_def_args(file, newNewIndex, List.append(acc, name)) (Ok(SymbolsToken("|")), _, newNewIndex) => Ok((acc, newNewIndex)) got => Err(format_error(got, ["`,`", "`|` to finish the function argument section"])) } } (Ok(SymbolsToken("|")), _, newIndex) => Ok((acc, newIndex)) got => Err(format_error(got, ["an identifier for one of the functions arguments", "`|` to finish the function argument section"])) } } parse_function : List(U8), U64 -> Try((FuncValue, U64), Str) parse_function = |file, index| { index2 = (match get_next_token(file, index) { (Ok(SymbolsToken("|")), _, i) => Ok(i) got => Err(format_error(got, ["`|` to start a function definition"])) })? (args, index3) = parse_function_def_args(file, index2, [])? index4 = (match get_next_token(file, index3) { (Ok(OpenBraceToken), _, i) => Ok(i) got => Err(format_error(got, ["`{` to start a function block"])) })? (body, index5) = parse_block(file, index4, [])? Ok(({ arguments: args, body: body }, index5)) } parse : List(U8), U64, List(TopLevel) -> Try(List(TopLevel), Str) parse = |utf8, index, acc| { match get_next_token(utf8, index) { (Ok(SnakeCaseIdentToken(name)), pos, index2) => { match get_next_token(utf8, index2) { (Ok(SymbolsToken(":")), _, index3) => { (type, index4) = parse_type(utf8, index3)? parse(utf8, index4, List.append(acc, TypeDefinition({ name: name, type: type, position: pos }))) } (Ok(SymbolsToken("=")), _, index3) => { (def, index4) = parse_function(utf8, index3)? parse(utf8, index4, List.append(acc, ValueDefinition({ name: name, value: def, position: pos }))) } got => { Err(format_error(got, ["`:`", "`=`"])) } } } (Ok(EndOfFileToken), _, _) => Ok(acc) got => {Err(format_error(got, ["a top level definition"]))} } }