From aa23bdaf994e84e92ea99fed2d1a024bee8589e1 Mon Sep 17 00:00:00 2001 From: Oskari Timperi Date: Wed, 1 Nov 2017 17:56:52 +0200 Subject: Reorganize code --- nimrec.nim | 158 ------------------------------------------------------ nimrec/parser.nim | 98 +++++++++++++++++++++++++++++++++ nimrec/record.nim | 64 ++++++++++++++++++++++ nimrec/utils.nim | 22 ++++++++ tests/test.nim | 8 +-- 5 files changed, 188 insertions(+), 162 deletions(-) delete mode 100644 nimrec.nim create mode 100644 nimrec/parser.nim create mode 100644 nimrec/record.nim create mode 100644 nimrec/utils.nim diff --git a/nimrec.nim b/nimrec.nim deleted file mode 100644 index 034bbfc..0000000 --- a/nimrec.nim +++ /dev/null @@ -1,158 +0,0 @@ -import streams -import strutils -import tables - -type - Field* = ref object - label*: string - value*: string - - Record* = ref object - fields: OrderedTableRef[string, seq[string]] - - ParseState {.pure.} = enum - Initial - Comment - Label - Value - ValueSkipSpace - FieldReady - - RecParser* = ref object - state: ParseState - field: Field - record: Record - - RecParseError* = object of Exception - -const - LabelFirstChar = {'a'..'z', 'A'..'Z', '%'} - - LabelChar = {'a'..'z', 'A'..'Z', '0'..'9', '_'} - - EofMarker = '\0' - -proc newRecParser*(): RecParser = - new(result) - result.state = ParseState.Initial - -proc newField(): Field = - new(result) - result.label = "" - result.value = "" - -proc newField(label, value: string): Field = - new(result) - result.label = label - result.value = value - -proc newRecord(): Record = - new(result) - result.fields = newOrderedTable[string, seq[string]]() - -proc feed*(parser: RecParser, ch: char, record: var Record): bool = - while true: - case parser.state - of ParseState.Initial: - case ch - of '#': - parser.state = ParseState.Comment - of '\l', EofMarker: - if parser.record != nil: - result = true - record = parser.record - parser.record = nil - of LabelFirstChar: - parser.state = ParseState.Label - parser.field = newField() - parser.field.label &= ch - else: - raise newException(RecParseError, "parse error: expected a comment, a label or an empty line") - of ParseState.Comment: - case ch - of '\l': - parser.state = ParseState.Initial - else: discard - of ParseState.Label: - case ch - of ':': - parser.state = ParseState.ValueSkipSpace - of LabelChar: - parser.field.label &= ch - else: - raise newException(RecParseError, - "parse error: invalid label char: " & ch) - of ParseState.Value: - case ch - of '\l': - let valueLen = len(parser.field.value) - if valueLen > 0 and parser.field.value[valueLen-1] == '\\': - setLen(parser.field.value, valueLen - 1) - else: - parser.state = ParseState.FieldReady - of EofMarker: - raise newException(RecParseError, - "parse error: value must be terminated by a newline") - else: - parser.field.value &= ch - of ParseState.ValueSkipSpace: - case ch - of (WhiteSpace - NewLines): - discard - else: - parser.field.value &= ch - parser.state = ParseState.Value - of ParseState.FieldReady: - case ch - of '+': - parser.state = ParseState.ValueSkipSpace - parser.field.value &= '\l' - else: - if parser.record == nil: - parser.record = newRecord() - if hasKey(parser.record.fields, parser.field.label): - add(parser.record.fields[parser.field.label], parser.field.value) - else: - add(parser.record.fields, parser.field.label, - @[parser.field.value]) - parser.field = nil - parser.state = ParseState.Initial - continue - - break - -proc `[]`*(record: Record, label: string): string = - result = record.fields[label][0] - -proc len*(record: Record): int = - result = len(record.fields) - -iterator records*(stream: Stream): Record = - let parser = newRecParser() - var record: Record - - while true: - var ch = readChar(stream) - - if feed(parser, ch, record): - yield record - - if ch == EofMarker: - break - -iterator pairs*(record: Record): (string, string) = - for label, values in record.fields: - for value in values: - yield (label, value) - -iterator items*(record: Record): Field = - for label, value in record: - yield newField(label, value) - -proc hasField*(record: Record, label: string): bool = - for field in record: - if field.label == label: - return true - -proc contains*(record: Record, label: string): bool = - result = hasField(record, label) diff --git a/nimrec/parser.nim b/nimrec/parser.nim new file mode 100644 index 0000000..de3a7f5 --- /dev/null +++ b/nimrec/parser.nim @@ -0,0 +1,98 @@ +import record + +import streams +import strutils + +type + ParseState {.pure.} = enum + Initial + Comment + Label + Value + ValueSkipSpace + FieldReady + + RecParser* = ref object + state: ParseState + field: Field + record: Record + + ParseError* = object of Exception + +const + LabelFirstChar = {'a'..'z', 'A'..'Z', '%'} + + LabelChar = {'a'..'z', 'A'..'Z', '0'..'9', '_'} + + EofMarker = '\0' + +proc newRecParser*(): RecParser = + new(result) + result.state = ParseState.Initial + +proc feed*(parser: RecParser, ch: char, record: var Record): bool = + while true: + case parser.state + of ParseState.Initial: + case ch + of '#': + parser.state = ParseState.Comment + of '\l', EofMarker: + if parser.record != nil: + result = true + record = parser.record + parser.record = nil + of LabelFirstChar: + parser.state = ParseState.Label + parser.field = newField() + parser.field.label &= ch + else: + raise newException(ParseError, "parse error: expected a comment, a label or an empty line") + of ParseState.Comment: + case ch + of '\l': + parser.state = ParseState.Initial + else: discard + of ParseState.Label: + case ch + of ':': + parser.state = ParseState.ValueSkipSpace + of LabelChar: + parser.field.label &= ch + else: + raise newException(ParseError, + "parse error: invalid label char: " & ch) + of ParseState.Value: + case ch + of '\l': + let valueLen = len(parser.field.value) + if valueLen > 0 and parser.field.value[valueLen-1] == '\\': + setLen(parser.field.value, valueLen - 1) + else: + parser.state = ParseState.FieldReady + of EofMarker: + raise newException(ParseError, + "parse error: value must be terminated by a newline") + else: + parser.field.value &= ch + of ParseState.ValueSkipSpace: + case ch + of (WhiteSpace - NewLines): + discard + else: + parser.field.value &= ch + parser.state = ParseState.Value + of ParseState.FieldReady: + case ch + of '+': + parser.state = ParseState.ValueSkipSpace + parser.field.value &= '\l' + else: + if parser.record == nil: + parser.record = newRecord() + addField(parser.record, parser.field) + parser.field = nil + parser.state = ParseState.Initial + continue + + break diff --git a/nimrec/record.nim b/nimrec/record.nim new file mode 100644 index 0000000..d8cbc8e --- /dev/null +++ b/nimrec/record.nim @@ -0,0 +1,64 @@ +import times +import strutils + +type + Field* = ref object + value*: string + label*: string + + Record* = ref object + fields: seq[Field] + +proc newField*(): Field = + new(result) + result.label = "" + result.value = "" + +proc newField*(label, value: string): Field = + new(result) + result.label = label + result.value = value + +proc newRecord*(): Record = + new(result) + result.fields = @[] + +proc getField*(record: Record, label: string): Field = + for field in record.fields: + if field.label == label: + return field + raise newException(KeyError, format("no such field: $1", label)) + +proc `[]`*(record: Record, label: string): string = + result = getField(record, label).value + +proc len*(record: Record): int = + result = len(record.fields) + +iterator pairs*(record: Record): (string, string) = + for field in record.fields: + yield (field.label, field.value) + +iterator items*(record: Record): Field = + for field in record.fields: + yield field + +iterator values*(record: Record, label: string): string = + for k, v in record: + if label == k: + yield v + +iterator labels*(record: Record): string = + for field in record: + yield field.label + +proc hasField*(record: Record, label: string): bool = + for field in record: + if field.label == label: + return true + +proc contains*(record: Record, label: string): bool = + result = hasField(record, label) + +proc addField*(record: Record, field: Field) = + add(record.fields, field) diff --git a/nimrec/utils.nim b/nimrec/utils.nim new file mode 100644 index 0000000..021a821 --- /dev/null +++ b/nimrec/utils.nim @@ -0,0 +1,22 @@ +import parser +import record + +import streams + +iterator records*(stream: Stream): Record = + let parser = newRecParser() + var record: Record + + while true: + var ch = readChar(stream) + + if feed(parser, ch, record): + yield record + + if ch == '\0': + break + +iterator records*(filename: string): Record = + let stream = newFileStream(filename) + for record in records(stream): + yield record diff --git a/tests/test.nim b/tests/test.nim index 5fb9821..70a9665 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -2,7 +2,7 @@ import unittest import streams import sequtils -import nimrec +import nimrec/[parser, record, recordset, utils] suite "parsing": test "basics": @@ -95,12 +95,12 @@ Name: Foobar! test "parse error if colon missing": let ss = newStringStream("Name\nAge: 34\n") - expect(RecParseError): + expect(ParseError): discard toSeq(records(ss)) test "parse error if invalid label": let ss = newStringStream("Name: John Doe\nFoo-bar: 111") - expect(RecParseError): + expect(ParseError): discard toSeq(records(ss)) test "label can start with %": @@ -114,7 +114,7 @@ Name: Foobar! test "field must be terminated by newline": let ss = newStringStream("%rec: Entry\n%type: Id int") - expect(RecParseError): + expect(ParseError): discard toSeq(records(ss)) test "multiple fields with same label": -- cgit v1.2.3