(* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. *) import cstdio, cstdlib, cstring, cctype, common, command_line_interface, lexer type SourceFile* = record buffer: [1024]Char; handle: ^FILE; size: Word; index: Word end StringBuffer* = record data: Pointer; size: Word; capacity: Word end SourceCode = record position: ElnaLocation; input: Pointer; empty: proc(stream: Pointer): Bool; advance: proc(stream: Pointer); head: proc(stream: Pointer): Char end Tokenizer* = record length: Word; data: ^^ElnaLexerToken end var stdout: ^FILE stderr: ^FILE stdin: ^FILE (* Standard procedures. *) proc reallocarray(ptr: Pointer; n: Word; size: Word): Pointer return realloc(ptr, n * size) proc substring(string: String; start: Word; count: Word): String return String(string.ptr + start, count) proc open_substring(string: String; start: Word): String return substring(string, start, string.length - start) proc string_dup(origin: String): String var copy: ^Char begin copy := malloc(origin.length); strncpy(copy, origin.ptr, origin.length) return String(copy, origin.length) proc string_buffer_new(): StringBuffer var result: StringBuffer begin result.capacity := 64u; result.data := malloc(result.capacity); result.size := 0u return result proc string_buffer_push(buffer: ^StringBuffer; char: Char) begin if buffer^.size >= buffer^.capacity then buffer^.capacity := buffer^.capacity + 1024u; buffer^.data := realloc(buffer^.data, buffer^.capacity) end; cast(buffer^.data + buffer^.size: ^Char)^ := cast(char: Char); buffer^.size := buffer^.size + 1u return proc string_buffer_pop(buffer: ^StringBuffer; count: Word) begin buffer^.size := buffer^.size - count return proc string_buffer_clear(buffer: ^StringBuffer): String var result: String begin result := String(cast(buffer^.data: ^Char), buffer^.size); buffer^.size := 0u return result (* Source code stream procedures. *) proc read_source(filename: ^Char): ^SourceFile var result: ^SourceFile file_handle: ^FILE begin file_handle := fopen(filename, "rb\0".ptr); if file_handle <> nil then result := cast(malloc(#size(SourceFile)): ^SourceFile); result^.handle := file_handle; result^.size := 0u; result^.index := 1u end return result proc source_file_empty(source_input: Pointer): Bool var source_file: ^SourceFile begin source_file := cast(source_input: ^SourceFile); if source_file^.index > source_file^.size then source_file^.size := fread(cast(@source_file^.buffer: Pointer), 1u, 1024u, source_file^.handle); source_file^.index := 1u end return source_file^.size = 0u proc source_file_head(source_input: Pointer): Char var source_file: ^SourceFile begin source_file := cast(source_input: ^SourceFile) return source_file^.buffer[source_file^.index] proc source_file_advance(source_input: Pointer) var source_file: ^SourceFile begin source_file := cast(source_input: ^SourceFile); source_file^.index := source_file^.index + 1u return proc source_code_empty(source_code: ^SourceCode): Bool return source_code^.empty(source_code^.input) proc source_code_head(source_code: SourceCode): Char return source_code.head(source_code.input) proc source_code_advance(source_code: ^SourceCode) begin source_code^.advance(source_code^.input); source_code^.position.column := source_code^.position.column return proc source_code_break(source_code: ^SourceCode) begin source_code^.position.line := source_code^.position.line + 1u; source_code^.position.column := 0u return proc source_code_expect(source_code: ^SourceCode; expected: Char): Bool return ~source_code_empty(source_code) & source_code_head(source_code^) = expected (* Token procedures. *) proc lexer_escape(escape: Char; result: ^Char): Bool var successful: Bool begin case escape of 'n': result^ := '\n'; successful := true | 't': result^ := '\t'; successful := true | 'f': result^ := '\f'; successful := true | 'r': result^ := '\r'; successful := true | 'v': result^ := '\v'; successful := true | '\\': result^ := '\\'; successful := true | '\'': result^ := '\''; successful := true | '"': result^ := '"'; successful := true | '0': result^ := '\0'; successful := true else successful := false end return successful (* Skip spaces. *) proc lexer_spaces(source_code: ^SourceCode) var current: Char begin while ~source_code_empty(source_code) & isspace(cast(source_code_head(source_code^): Int)) <> 0 do current := source_code_head(source_code^); if current = '\n' then source_code_break(source_code) end; source_code_advance(source_code) end return (* Checker whether the character is allowed in an identificator. *) proc lexer_is_ident(char: Char): Bool return isalnum(cast(char: Int)) <> 0 or char = '_' proc lexer_identifier(source_code: ^SourceCode; token_content: ^StringBuffer) var content_length: Word begin while ~source_code_empty(source_code) & lexer_is_ident(source_code_head(source_code^)) do string_buffer_push(token_content, source_code_head(source_code^)); source_code_advance(source_code) end return proc lexer_comment(source_code: ^SourceCode; token_content: ^StringBuffer): Bool var trailing: Word begin trailing := 0u; while ~source_code_empty(source_code) & trailing < 2u do if source_code_head(source_code^) = '*' then string_buffer_push(token_content, '*'); trailing := 1u elsif source_code_head(source_code^) = ')' & trailing = 1u then string_buffer_pop(token_content, 1u); trailing := 2u else string_buffer_push(token_content, source_code_head(source_code^)); trailing := 0u end; source_code_advance(source_code) end return trailing = 2u proc lexer_character(source_code: ^SourceCode; token_content: ^Char): Bool var successful: Bool begin successful := ~source_code_empty(source_code); if successful then if source_code_head(source_code^) = '\\' then source_code_advance(source_code); successful := ~source_code_empty(source_code) & lexer_escape(source_code_head(source_code^), token_content) else token_content^ := source_code_head(source_code^); successful := true end end; if successful then source_code_advance(source_code) end return successful proc lexer_string(source_code: ^SourceCode; token_content: ^StringBuffer): Bool var token_end, constructed_string: ^Char token_length: Word is_valid: Bool := true next_char: Char begin while is_valid & ~source_code_empty(source_code) & source_code_head(source_code^) <> '"' do is_valid := lexer_character(source_code, @next_char); if is_valid then string_buffer_push(token_content, next_char) end end; if is_valid & source_code_expect(source_code, '"') then source_code_advance(source_code) else is_valid := false end return is_valid proc lexer_number(source_code: ^SourceCode; token_content: ^Int) begin token_content^ := 0; while ~source_code_empty(source_code) & isdigit(cast(source_code_head(source_code^): Int)) <> 0 do token_content^ := token_content^ * 10 + (cast(source_code_head(source_code^): Int) - cast('0': Int)); source_code_advance(source_code) end return (* Categorize an identifier. *) proc lexer_categorize(token_content: String): ^ElnaLexerToken var current_token: ^ElnaLexerToken begin if token_content = "if" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._if elsif token_content = "then" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._then elsif token_content = "else" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._else elsif token_content = "elsif" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._elsif elsif token_content = "while" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._while elsif token_content = "do" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._do elsif token_content = "proc" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._proc elsif token_content = "begin" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._begin elsif token_content = "end" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._end elsif token_content = "extern" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._extern elsif token_content = "const" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._const elsif token_content = "var" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._var elsif token_content = "case" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._case elsif token_content = "of" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._of elsif token_content = "type" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._type elsif token_content = "record" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._record elsif token_content = "true" then current_token := malloc(#size(ElnaLexerBooleanToken)); current_token^.kind := ElnaLexerKind.boolean; cast(current_token: ^ElnaLexerBooleanToken)^.value := true elsif token_content = "false" then current_token := malloc(#size(ElnaLexerBooleanToken)); current_token^.kind := ElnaLexerKind.boolean; cast(current_token: ^ElnaLexerBooleanToken)^.value := false elsif token_content = "nil" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.null elsif token_content = "or" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._or elsif token_content = "return" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._return elsif token_content = "cast" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._cast elsif token_content = "defer" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._defer elsif token_content = "import" then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind._import else current_token := malloc(#size(ElnaLexerStringToken)); current_token^.kind := ElnaLexerKind.identifier; cast(current_token: ^ElnaLexerStringToken)^.value := string_dup(token_content) end return current_token proc lexer_add_token(lexer: ^Tokenizer; token: ^ElnaLexerToken) var new_length: Word begin new_length := lexer^.length + 1u; lexer^.data := cast(reallocarray(cast(lexer^.data: Pointer), new_length, #size(Pointer)): ^^ElnaLexerToken); (lexer^.data + lexer^.length)^ := token; lexer^.length := new_length return (* Read the next token from the input. *) proc lexer_next(source_code: SourceCode; token_buffer: ^StringBuffer): ^ElnaLexerToken var current_token: ^ElnaLexerToken := nil first_char: Char begin first_char := source_code_head(source_code); if isalpha(cast(first_char: Int)) <> 0 or first_char = '_' then lexer_identifier(@source_code, token_buffer); current_token := lexer_categorize(string_buffer_clear(token_buffer)) elsif first_char = '#' then source_code_advance(@source_code); lexer_identifier(@source_code, token_buffer); current_token := malloc(#size(ElnaLexerStringToken)); current_token^.kind := ElnaLexerKind.trait; cast(current_token: ^ElnaLexerStringToken)^.value := string_dup(string_buffer_clear(token_buffer)) elsif isdigit(cast(first_char: Int)) <> 0 then current_token := malloc(#size(ElnaLexerIntegerToken)); lexer_number(@source_code, @cast(current_token: ^ElnaLexerIntegerToken)^.value); if source_code_expect(@source_code, 'u') then current_token^.kind := ElnaLexerKind.word; source_code_advance(@source_code) else current_token^.kind := ElnaLexerKind.integer end elsif first_char = '(' then source_code_advance(@source_code); if source_code_empty(@source_code) then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.left_paren elsif source_code_head(source_code) = '*' then source_code_advance(@source_code); if lexer_comment(@source_code, token_buffer) then current_token := malloc(#size(ElnaLexerStringToken)); cast(current_token: ^ElnaLexerStringToken)^.value := string_dup(string_buffer_clear(token_buffer)); current_token^.kind := ElnaLexerKind.comment end else current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.left_paren end elsif first_char = ')' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.right_paren; source_code_advance(@source_code) elsif first_char = '{' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.left_brace; source_code_advance(@source_code) elsif first_char = '}' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.right_brace; source_code_advance(@source_code) elsif first_char = '\'' then source_code_advance(@source_code); if lexer_character(@source_code, @first_char) & source_code_expect(@source_code, '\'') then current_token := malloc(#size(ElnaLexerCharacterToken)); current_token^.kind := ElnaLexerKind.character; cast(current_token: ^ElnaLexerCharacterToken)^.value := first_char; source_code_advance(@source_code) end elsif first_char = '"' then source_code_advance(@source_code); if lexer_string(@source_code, token_buffer) then current_token := malloc(#size(ElnaLexerStringToken)); current_token^.kind := ElnaLexerKind.string; cast(current_token: ^ElnaLexerStringToken)^.value := string_dup(string_buffer_clear(token_buffer)) end elsif first_char = '[' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.left_square; source_code_advance(@source_code) elsif first_char = ']' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.right_square; source_code_advance(@source_code) elsif first_char = '>' then source_code_advance(@source_code); current_token := malloc(#size(ElnaLexerToken)); if source_code_empty(@source_code) then current_token^.kind := ElnaLexerKind.greater_than elsif source_code_head(source_code) = '=' then current_token^.kind := ElnaLexerKind.greater_equal; source_code_advance(@source_code) elsif source_code_head(source_code) = '>' then current_token^.kind := ElnaLexerKind.shift_right; source_code_advance(@source_code) else current_token^.kind := ElnaLexerKind.greater_than end elsif first_char = '<' then source_code_advance(@source_code); current_token := malloc(#size(ElnaLexerToken)); if source_code_empty(@source_code) then current_token^.kind := ElnaLexerKind.less_than elsif source_code_head(source_code) = '=' then current_token^.kind := ElnaLexerKind.less_equal; source_code_advance(@source_code) elsif source_code_head(source_code) = '<' then current_token^.kind := ElnaLexerKind.shift_left; source_code_advance(@source_code) elsif source_code_head(source_code) = '>' then current_token^.kind := ElnaLexerKind.not_equal; source_code_advance(@source_code) else current_token^.kind := ElnaLexerKind.less_than end elsif first_char = '=' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.equals; source_code_advance(@source_code) elsif first_char = ';' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.semicolon; source_code_advance(@source_code) elsif first_char = '.' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.dot; source_code_advance(@source_code) elsif first_char = ',' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.comma; source_code_advance(@source_code) elsif first_char = '+' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.plus; source_code_advance(@source_code) elsif first_char = '-' then source_code_advance(@source_code); current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.minus elsif first_char = '*' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.multiplication; source_code_advance(@source_code) elsif first_char = '/' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.division; source_code_advance(@source_code) elsif first_char = '%' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.remainder; source_code_advance(@source_code) elsif first_char = ':' then source_code_advance(@source_code); current_token := malloc(#size(ElnaLexerToken)); if source_code_empty(@source_code) then current_token^.kind := ElnaLexerKind.colon elsif source_code_head(source_code) = '=' then current_token^.kind := ElnaLexerKind.assignment; source_code_advance(@source_code) else current_token^.kind := ElnaLexerKind.colon end elsif first_char = '^' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.hat; source_code_advance(@source_code) elsif first_char = '@' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.at; source_code_advance(@source_code) elsif first_char = '!' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.exclamation; source_code_advance(@source_code) elsif first_char = '&' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.and; source_code_advance(@source_code) elsif first_char = '~' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.not; source_code_advance(@source_code) elsif first_char = '|' then current_token := malloc(#size(ElnaLexerToken)); current_token^.kind := ElnaLexerKind.pipe; source_code_advance(@source_code) end return current_token (* Split the source text into tokens. *) proc lexer_text(source_code: SourceCode): Tokenizer var current_token: ^ElnaLexerToken token_buffer: StringBuffer lexer: Tokenizer begin lexer := Tokenizer{ length: 0u, data: nil }; token_buffer := string_buffer_new(); lexer_spaces(@source_code); while ~source_code_empty(@source_code) do current_token := lexer_next(source_code, @token_buffer); if current_token <> nil then lexer_add_token(@lexer, current_token); lexer_spaces(@source_code) else write_s("Lexical analysis error on \""); write_c(source_code_head(source_code)); write_s("\".\n") end end return lexer (* Parser. *) proc parse(tokens: ^^ElnaLexerToken; tokens_size: Word) var current_token: ^ElnaLexerToken i: Word := 0u begin while i < tokens_size do current_token := (tokens + i)^; case current_token^.kind of ElnaLexerKind._if: write_s("IF") | ElnaLexerKind._then: write_s("THEN") | ElnaLexerKind._else: write_s("ELSE") | ElnaLexerKind._elsif: write_s("ELSIF") | ElnaLexerKind._while: write_s("WHILE") | ElnaLexerKind._do: write_s("DO") | ElnaLexerKind._proc: write_s("PROC") | ElnaLexerKind._begin: write_s("BEGIN") | ElnaLexerKind._end: write_s("END") | ElnaLexerKind._extern: write_s("EXTERN") | ElnaLexerKind._const: write_s("CONST") | ElnaLexerKind._var: write_s("VAR") | ElnaLexerKind._case: write_s("CASE") | ElnaLexerKind._of: write_s("OF") | ElnaLexerKind._type: write_s("TYPE") | ElnaLexerKind._record: write_s("RECORD") | ElnaLexerKind.pipe: write_s("|") | ElnaLexerKind.boolean: write_s("BOOLEAN<"); write_b(cast(current_token: ^ElnaLexerBooleanToken)^.value); write_c('>') | ElnaLexerKind.null: write_s("NIL") | ElnaLexerKind.and: write_s("&") | ElnaLexerKind._or: write_s("OR") | ElnaLexerKind.not: write_s("~") | ElnaLexerKind._return: write_s("RETURN") | ElnaLexerKind._cast: write_s("CAST") | ElnaLexerKind.shift_left: write_s("<<") | ElnaLexerKind.shift_right: write_s(">>") | ElnaLexerKind.identifier: write_c('<'); write_s(cast(current_token: ^ElnaLexerStringToken)^.value); write_c('>') | ElnaLexerKind.trait: write_c('#'); write_s(cast(current_token: ^ElnaLexerStringToken)^.value) | ElnaLexerKind.left_paren: write_s("(") | ElnaLexerKind.right_paren: write_s(")") | ElnaLexerKind.left_square: write_s("[") | ElnaLexerKind.right_square: write_s("]") | ElnaLexerKind.greater_equal: write_s(">=") | ElnaLexerKind.less_equal: write_s("<=") | ElnaLexerKind.greater_than: write_s(">") | ElnaLexerKind.less_than: write_s("<") | ElnaLexerKind.equals: write_s("=") | ElnaLexerKind.not_equal: write_s("<>") | ElnaLexerKind.semicolon: write_c(';') | ElnaLexerKind.dot: write_c('.') | ElnaLexerKind.comma: write_c(',') | ElnaLexerKind.plus: write_c('+') | ElnaLexerKind.minus: write_c('-') | ElnaLexerKind.multiplication: write_c('*') | ElnaLexerKind.division: write_c('/') | ElnaLexerKind.remainder: write_c('%') | ElnaLexerKind.assignment: write_s(":=") | ElnaLexerKind.colon: write_c(':') | ElnaLexerKind.hat: write_c('^') | ElnaLexerKind.at: write_c('@') | ElnaLexerKind.comment: write_s("(* COMMENT *)") | ElnaLexerKind.integer: write_c('<'); write_i(cast(current_token: ^ElnaLexerIntegerToken)^.value); write_c('>') | ElnaLexerKind.word: write_c('<'); write_i(cast(current_token: ^ElnaLexerIntegerToken)^.value); write_s("u>") | ElnaLexerKind.character: write_c('<'); write_i(cast(cast(current_token: ^ElnaLexerCharacterToken)^.value: Int)); write_s("c>") | ElnaLexerKind.string: write_s("\"...\"") | ElnaLexerKind._defer: write_s("DEFER") | ElnaLexerKind.exclamation: write_c('!') | ElnaLexerKind._import: write_s("IMPORT") else write_s("UNKNOWN<"); write_i(cast(current_token^.kind: Int)); write_c('>') end; write_c(' '); i := i + 1u end; write_c('\n') return (* Compilation entry. *) proc compile_in_stages(command_line: ^CommandLine; source_code: SourceCode): Int var return_code: Int := 0 lexer: Tokenizer begin if command_line^.lex or command_line^.parse then lexer := lexer_text(source_code) end; if command_line^.parse then parse(lexer.data, lexer.length) end return return_code proc process(argc: Int; argv: ^^Char): Int var tokens: ^ElnaLexerToken tokens_size: Word source_code: SourceCode command_line: ^CommandLine return_code: Int := 0 source_file: ^SourceFile begin command_line := parse_command_line(argc, argv); if command_line = nil then return_code := 2 end; if return_code = 0 then source_file := read_source(command_line^.input); if source_file = nil then perror(command_line^.input); return_code := 3 end end; if return_code = 0 then defer fclose(source_file^.handle) end; source_code.position := ElnaLocation{line: 1u, column: 1u}; source_code.input := source_file; source_code.empty := source_file_empty; source_code.head := source_file_head; source_code.advance := source_file_advance; return_code := compile_in_stages(command_line, source_code) end return return_code proc initialize_global_state() begin stdin := fdopen(0, "r\0".ptr); stdout := fdopen(1, "w\0".ptr); stderr := fdopen(2, "w\0".ptr) return begin initialize_global_state(); exit(process(count, parameters)) end.