diff --git a/braces.go b/braces.go new file mode 100644 index 0000000..b8458a9 --- /dev/null +++ b/braces.go @@ -0,0 +1,6 @@ +package brace + +const ( + leftbrace rune = '{' + rightbrace rune = '}' +) diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..d6fe195 --- /dev/null +++ b/errors.go @@ -0,0 +1,31 @@ +package brace + +import ( + "sourcecode.social/reiver/go-erorr" +) + +const ( + errEmptyString = erorr.Error("empty string") + errNilRuneScanner = erorr.Error("brace: nil rune-scanner") + errNilWriter = erorr.Error("brace: nil writer") +) + +func errFileEndedBeforeBraceStringLiteralClosed(depth int64) error { + return erorr.Errorf("file ended before the brace-string literal was closed — expected %d more %q (%U) character(s)", depth, rightbrace, rightbrace) +} + +func errInternalError(err error) error { + return erorr.Errorf("brace: internal error: %w", err) +} + +func errParserDepthNegative(depth int64) error { + return erorr.Errorf("brace: parser depth (%d) is negative", depth) +} + +func errProblemReadingCharacterNumber(num uint64, err error) error { + return erorr.Errorf("brace: problem reading character №%d of what should have been a brace-string literal: %w", num, err) +} + +func errProblemUnreadingCharacterNumber(num uint64, err error, r rune) error { + return erorr.Errorf("brace: problem unreading character №%d (which is a %q (%U)) of what should have been a brace-string literal: %w", num, r, r, err) +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..9b1eb4b --- /dev/null +++ b/parse.go @@ -0,0 +1,109 @@ +package brace + +import ( + "io" + + "sourcecode.social/reiver/go-erorr" +) + +// Parse parses a brace-string literal from a io.RuneScanner. +// +// Parse will call ‘fn’ for each logical character it receives. +// So — if this is the brace-literal it parses: +// +// `{a b \{ c \} d}` +// +// Then ‘fn’ would be called 11 times, and given these runes: +// +// 'a' +// ' ' +// 'b' +// ' ' +// '{' +// ' ' +// 'c' +// ' ' +// '}' +// ' ' +// 'd' +// +// Note that the beginning '{' and ending '}' of the brace-string literal are not part of this. +// Also note that the '\' (black-slash) before the '{' and the '}' was not included either. +func Parse(fn func(rune)error, runescanner io.RuneScanner) error { + + if nil == runescanner { + return errNilRuneScanner + } + + var nextRead uint64 = 1 + + { + r, _, err := runescanner.ReadRune() + if io.EOF == err { + return errProblemReadingCharacterNumber(nextRead, errEmptyString) + } + if nil != err { + return errProblemReadingCharacterNumber(nextRead, err) + } + nextRead++ + + { + const expected = leftbrace + actual := r + + if expected != actual { + err := runescanner.UnreadRune() + if nil != err { + return errProblemUnreadingCharacterNumber(nextRead, errEmptyString, r) + } + + return erorr.Errorf("brace: expected first character of brace-string literal to be a %q (%U), but actually was %q (%U)", expected, expected, actual, actual) + } + } + } + + var depth int64 = 1 + loop: for { + r, _, err := runescanner.ReadRune() + if io.EOF == err { + return errProblemReadingCharacterNumber(nextRead, errFileEndedBeforeBraceStringLiteralClosed(depth)) + } + if nil != err { + return errProblemReadingCharacterNumber(nextRead, err) + } + nextRead++ + + switch r { + case leftbrace: + depth++ + case rightbrace: + depth-- + case '\\': + r, _, err = runescanner.ReadRune() + if io.EOF == err { + return errProblemReadingCharacterNumber(nextRead, errFileEndedBeforeBraceStringLiteralClosed(depth)) + } + if nil != err { + return erorr.Errorf("brace: problem reading character №%d (which would have followed a black-slash %q (%U) that was just read) of what should have been a brace-string literal: %w", nextRead, r, r, err) + } + nextRead++ + } + + switch { + case 0 == depth: + ////////////// BREAK + break loop + case depth < 0: + return errInternalError(errParserDepthNegative(depth)) + } + + { + err := fn(r) + if nil != err { + return err + } + } + } + + return nil +}