From: Brendan Hansen Date: Mon, 13 Feb 2023 03:10:45 +0000 (-0600) Subject: documentation in CSV library X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=c626c50a1365ec071a6e343eddf7d1c6ba8fdbb9;p=onyx.git documentation in CSV library --- diff --git a/compiler/src/symres.c b/compiler/src/symres.c index c8d36406..ee361620 100644 --- a/compiler/src/symres.c +++ b/compiler/src/symres.c @@ -1397,7 +1397,8 @@ static SymresStatus symres_process_directive(AstNode* directive) { } if (ofunc->kind != Ast_Kind_Overloaded_Function) { - onyx_report_error(add_overload->token->pos, Error_Critical, "#match directive expects a matched procedure."); + onyx_report_error(add_overload->token->pos, Error_Critical, "#match directive expects a matched procedure, got %s.", + onyx_ast_node_kind_string(ofunc->kind)); return Symres_Error; } @@ -1411,6 +1412,7 @@ static SymresStatus symres_process_directive(AstNode* directive) { if (!token_same_file(add_overload->token, ofunc->token)) { onyx_report_error(add_overload->token->pos, Error_Critical, "Cannot add match option here as this option is not within the same file as the original #match declared with #local."); onyx_report_error(ofunc->token->pos, Error_Critical, "Here is the original #match."); + return Symres_Error; } } diff --git a/core/container/iter.onyx b/core/container/iter.onyx index b481b77b..5cabaf3a 100644 --- a/core/container/iter.onyx +++ b/core/container/iter.onyx @@ -60,7 +60,7 @@ close :: (it: Iterator) { // // Implicit iterator creation // -// The following overloads of as_iter all for an automatic +// The following overloads of as_iter allow for an automatic // definition of how to declare an iterator, provided the // type has the necessary methods. // diff --git a/core/encoding/csv.onyx b/core/encoding/csv.onyx index 62665648..9fdf3d16 100644 --- a/core/encoding/csv.onyx +++ b/core/encoding/csv.onyx @@ -1,5 +1,14 @@ package core.encoding.csv +// +// A simple CSV parsing and encoding library. +// +// This library is used to ingest and output a CSV file. It uses +// a polymorphic structure to represent a CSV over a particular +// set of data. This helps with type safety, as well as making +// it ergonomic to work with. +// + use core {string, array, iter, conv, io} use core.misc {any_as} use runtime.info { @@ -7,15 +16,29 @@ use runtime.info { Type_Info_Struct } +// +// Represents data from a CSV file of a particular type. CSV :: struct (Output_Type: type_expr) { entries: [..] Output_Type; } +// +// Tag-type used to tell the ingress and egress methods what +// the column name of a particular data element should be. +// +// Data :: struct { +// @CSV_Column.{"Actual Column Name"} +// variable_name: str; +// } CSV_Column :: struct { name: str; } +// +// Define methods used with the CSV structure. #inject CSV { + // + // Create and initialize a CSV with no elements. make :: ($T: type_expr) => { r := CSV(T).{}; r.entries = make(typeof r.entries); @@ -23,24 +46,44 @@ CSV_Column :: struct { return r; } + // + // Frees all data in a CSV. delete :: (csv: ^CSV) { delete(^csv.entries); } + + // + // Ingests data from a string representing CSV data. + // Uses the type of the CSV to know what columns should be expected. + // If `headers_presents` is true, the first line will be treated as + // headers, and cross checked with the CSV_Column tag information. + // Use this when the columns from your CSV have a different order + // from the order of fields in the structure. + ingress_string :: (csv: ^CSV, contents: str, headers_present := true) -> bool { + reader, stream := io.reader_from_string(contents); + defer cfree(stream); + + return csv->ingress(^reader, headers_present); + } - ingress :: (csv: ^CSV, contents: str, headers_present := true) -> bool { + // + // Ingests data from an Reader containing CSV data. + // Uses the type of the CSV to know what columns should be expectd. + ingress :: (csv: ^CSV, reader: ^io.Reader, headers_present := true) -> bool { Header :: struct { type: type_expr; offset: i32; } - s := contents; any_headers := make([..] Header); defer delete(^any_headers); output_type_info: ^Type_Info_Struct = ~~ get_type_info(csv.Output_Type); if headers_present { - header_line, s' := string.bisect(s, #char "\n"); + header_line := reader->read_line(allocator=context.temp_allocator) + |> string.strip_trailing_whitespace(); + for header: string.split_iter(header_line, #char ",") { member := array.first(output_type_info.members, #(do { if tag := array.first(it.tags, #(it.type == CSV_Column)); tag { @@ -59,12 +102,13 @@ CSV_Column :: struct { } } - for line: string.split_iter(s, #char "\n") { + for line: reader->lines() { + defer string.free(line); + out: csv.Output_Type; - for entry: - string.split_iter(line, #char ",") - |> iter.enumerate() + for entry: string.split_iter(line, #char ",") + |> iter.enumerate() { header := ^any_headers[entry.index]; if header.offset == -1 do continue; @@ -82,6 +126,10 @@ CSV_Column :: struct { } } + // + // Outputs data from a CSV into a Writer. + // When `include_headers` is true, the first line outputted will be + // the headers of the CSV, according to the CSV_Column tag information. egress :: (csv: ^CSV, writer: ^io.Writer, include_headers := true) { output_type_info: ^Type_Info_Struct = ~~ get_type_info(csv.Output_Type); @@ -111,3 +159,33 @@ CSV_Column :: struct { } } + +// +// Example and test case +// + +@core.test.test.{"CSV Test"} +(t: ^core.test.T) { + data := """first,second,third +1,test 1,1.2 +2,test 2,2.4 +3,test 3,3.6"""; + + Data :: struct { + @CSV_Column.{"first"} + first: i32; + + @CSV_Column.{"second"} + second: str; + + @CSV_Column.{"third"} + third: f32; + } + + csv: CSV(Data); + csv->ingress_string(data); + + t->assert(csv.entries[0].first == 1, "First entry matches"); + t->assert(csv.entries[2].third == 3.6, "Last entry matches"); +} + diff --git a/core/encoding/ini.onyx b/core/encoding/ini.onyx index 73a1fedb..17b59e0c 100644 --- a/core/encoding/ini.onyx +++ b/core/encoding/ini.onyx @@ -5,19 +5,23 @@ use core { aprintf } -#local { - IniParseResult :: enum { - Success; - Error; - } +IniParseResult :: enum { + Success; + Error; +} - IniParseError :: struct { - msg: str; - line: u32; - } +IniParseError :: struct { + msg: str; + line: u32; +} + +parse_ini_file :: macro (r: ^io.Reader, output_ptr: ^$T) => { + parse_ini_file_inner :: parse_ini_file_inner + return parse_ini_file_inner(r, output_ptr); } -parse_ini_file :: (r: ^io.Reader, output_ptr: any) -> (IniParseResult, IniParseError) { +#local +parse_ini_file_inner :: (r: ^io.Reader, output_ptr: any) -> (IniParseResult, IniParseError) { info :: runtime.info line := 1;