shithub: mc

Download patch

ref: 2cf555f110ec6d21a92ef3dfaeb5128c9fc3fd2d
parent: ba6015188abeb957de22a4412babc97d4587b4b9
author: Ori Bernstein <ori@eigenstate.org>
date: Wed Sep 23 17:18:54 EDT 2015

Return parse errors.

--- a/lib/date/parse.myr
+++ b/lib/date/parse.myr
@@ -6,28 +6,55 @@
 use "zoneinfo.use"
 
 pkg date =
+	type parsefail = union
+		`Doublefmt char
+		`Badsep (char, char)
+		`Badfmt char
+		`Badzone byte[:]
+		`Badname byte[:]
+		`Badchar
+		`Badampm
+		`Shortint
+		`Badint
+	;;
+
 	/* date i/o */
-	const parsefmt	: (f : byte[:], s: byte[:]	-> std.option(instant))
-	const parsefmtl	: (f : byte[:], s: byte[:]	-> std.option(instant))
-	const parsefmtz	: (f : byte[:], s: byte[:], tz : byte[:]	-> std.option(instant))
+	const parsefmt	: (f : byte[:], s: byte[:]	-> std.result(instant, parsefail))
+	const parsefmtl	: (f : byte[:], s: byte[:]	-> std.result(instant, parsefail))
+	const parsefmtz	: (f : byte[:], s: byte[:], tz : byte[:]	-> std.result(instant, parsefail))
 ;;
 
 const UnixJulianDiff	= 719468
 
-const parsefmt	= {f, s;	-> parse(f, s, "", false)}
-const parsefmtl = {f, s;	-> parse(f, s, "local", true)}
-const parsefmtz = {f, s, tz;	-> parse(f, s, tz, true)}
+const parsefmt	= {f, s;	-> strparse(f, s, "", false)}
+const parsefmtl = {f, s;	-> strparse(f, s, "local", true)}
+const parsefmtz = {f, s, tz;	-> strparse(f, s, tz, true)}
 
-const parse = {f, s, tz, replace
+type parsedtz = union
+	`None
+	`Off duration
+	`Name byte[:]
+;;
+
+const __init__ = {
+	var fail : parsefail
+	std.fmtinstall(std.typeof(fail), failfmt, [][:])
+}
+
+const strparse = {f, s, tz, replace
 	var d, err
+	var seen
 
 	d = [.year = 0]
-	err = false
-	s = filldate(&d, f, s, &err)
+	err = `std.None
+	seen = std.mkbs()
+	s = filldate(&d, f, s, seen, &err)
+	std.bsfree(seen)
 
 	d.actual -= d.tzoff castto(std.time)
-	if err || s.len > 0
-		-> `std.None
+	match err
+	| `std.Some e:	-> `std.Fail e
+	| `std.None:	/* no error, we're ok */
 	;;
 
 	if replace
@@ -34,17 +61,10 @@
 		d = mkinstant(d.actual, tz)
 	;;
 
-	-> `std.Some d
+	-> `std.Ok d
 }
 
-type parsedtz = union
-	`None
-	`Off duration
-	`Name byte[:]
-;;
-
-
-const filldate = {d, f, s, err -> byte[:]
+const filldate = {d, f, s, seen, err : std.option(parsefail)#
 	var fc, sc, z
 
 	z = ""
@@ -52,6 +72,11 @@
 		(fc, f) = std.striter(f)
 		if fc == '%'
 			(fc, f) = std.striter(f)
+			if std.bshas(seen, fc)
+				err# = `std.Some `Doublefmt fc
+				-> s
+			;;
+			std.bsput(seen, fc)
 			match fc
 			/* named things */
 			| 'a':	s = indexof(&d.day, s, _names.abbrevday, err)
@@ -58,14 +83,14 @@
 			| 'A':	s = indexof(&d.day, s, _names.fullday, err)
 			| 'b':	s = indexof(&d.mon, s, _names.abbrevmon, err)
 			| 'B':	s = indexof(&d.mon, s, _names.fullmon, err)
-			| 'c':	s = filldate(d, "%Y-%m-%d", s, err)
+			| 'c':	s = filldate(d, "%Y-%m-%d", s, seen, err)
 			| 'C':	
 				s = intval(&d.year, s, 2, 2, err)
 				d.year += 1900
 			| 'd':	s = intval(&d.day, s, 2, 2, err)
-			| 'D':	s = filldate(d, "%m/%d/%y", s, err)
+			| 'D':	s = filldate(d, "%m/%d/%y", s, seen, err)
 			| 'e':	s = intval(&d.day, s, 1, 2, err)
-			| 'F':	s = filldate(d, "%y-%m-%d", s, err)
+			| 'F':	s = filldate(d, "%y-%m-%d", s, seen, err)
 			| 'h':	s = indexof(&d.day, s, _names.abbrevmon, err)
 			| 'H':	s = intval(&d.h, s, 1, 2, err)
 			| 'I':	s = intval(&d.h, s, 1, 2, err)
@@ -73,17 +98,16 @@
 			| 'l':	s = intval(&d.h, s, 1, 2, err)
 			| 'm':	s = intval(&d.mon, s, 1, 2, err)
 			| 'M':	s = intval(&d.m, s, 1, 2, err)
-			| 'n':	s = matchstr(s, "\n", err)
 			| 'p':	s = matchampm(d, s, err)
 			| 'P':	s = matchampm(d, s, err)
-			| 'r':	s = filldate(d, "%H:%M:%S %P", s, err) 
-			| 'R':	s = filldate(d, "%H:%M %P", s, err)
+			| 'r':	s = filldate(d, "%H:%M:%S %P", s, seen, err) 
+			| 'R':	s = filldate(d, "%H:%M %P", s, seen, err)
 			| 's':	s = intval(&d.actual, s, 1, 64, err)
 			| 'S':	s = intval(&d.s, s, 1, 2, err)
 			| 't':	s = eatspace(s)
 			| 'u':	s = intval(&d.wday, s, 1, 1, err)
-			| 'x':	s = filldate(d, Datefmt, s, err)
-			| 'X':	s = filldate(d, Timefmt, s, err)
+			| 'x':	s = filldate(d, Datefmt, s, seen, err)
+			| 'X':	s = filldate(d, Timefmt, s, seen, err)
 			| 'y':	s = intval(&d.year, s, 1, 2, err)
 				d.year += 1900
 			| 'Y':	
@@ -95,15 +119,16 @@
 			;;
 		else
 			(sc, s) = std.striter(s)
-			if std.isspace(sc)
+			if std.isspace(fc) && std.isspace(sc)
 				s = eatspace(s)
-			elif (sc != fc)
-				err# = true
+			elif sc != fc
+				err# = `std.Some `Badsep (fc, sc)
 				-> s
 			;;
 		;;
-		if err#
-			-> s
+		match err#
+		| `std.Some _:	-> s
+		| `std.None:
 		;;
 	;;
 	d.actual = recalc(d)
@@ -110,7 +135,7 @@
 	if z.len > 0
 		match _zoneinfo.findtzoff(z, d.actual)
 		| `std.Some o:	d.tzoff = o
-		| `std.None:	err# = true
+		| `std.None:	err# = `std.Some `Badzone z
 		;;
 	;;
 	-> s
@@ -133,7 +158,7 @@
 			-> s
 		;;
 	;;
-	err# = true
+	err# = `std.Some `Badname s
 	dst# = 0
 	-> s
 }
@@ -143,7 +168,7 @@
 	var tzoff
 
 	if s.len < 1
-		err# = true
+		err# = `std.Some `Badzone s
 		-> ""
 	;;
 	if std.sleq(s[:1], "-")
@@ -151,8 +176,8 @@
 	elif std.sleq(s[:1], "+") 
 		sgn = 1
 	else
-		err# = true
-		-> ""
+		err# = `std.Some `Badzone s
+		-> s
 	;;
 	s = intval(&tzoff, s[1:], 2, 4, err) 
 	dst# = sgn * (tzoff / 100) * 3600 * 1_000_000 + (tzoff % 100) * 60 * 1_000_000
@@ -172,7 +197,7 @@
 	if n < d._tzbuf.len
 		std.slcp(d._tzbuf[:n], s[:n])
 	else
-		err# = true
+		err# = `std.Some `Badzone s[:n]
 	;;
 	-> (s[n:], s[:n])
 }
@@ -180,8 +205,8 @@
 
 const matchstr = {s, str, err
 	if s.len <= str.len || !std.sleq(s[:str.len], str)
-		err# = true
-		-> ""
+		err# = `std.Some `Badchar
+		-> s
 	;;
 	-> s[str.len:]
 }
@@ -188,7 +213,7 @@
 
 const matchampm = {d, s, err
 	if s.len < 2
-		err# = true
+		err# = `std.Some `Badampm
 		-> s
 	;;
 	if std.sleq(s[:2], "am") || std.sleq(s[:2], "AM")
@@ -197,12 +222,13 @@
 		d.h += 12
 		-> s[2:]
 	else
-		err# = true
+		err# = `std.Some `Badampm
 		-> s
 	;;
 }
 generic intval = {dst : @a::(numeric,integral)#, s : byte[:], \
-		min : @a::(numeric,integral), max : @a::(numeric,integral), err : bool# -> byte[:]
+		min : @a::(numeric,integral), max : @a::(numeric,integral), \
+		err : std.option(parsefail)# -> byte[:]
 	var i
 	var c
 	var num
@@ -211,7 +237,7 @@
 	for i = 0; i < min; i++
 		(c, s) = std.striter(s)
 		if !std.isdigit(c)
-			err# = true
+			err# = `std.Some `Shortint
 			-> s
 		;;
 	;;
@@ -230,8 +256,21 @@
 		dst# = v
 		-> s
 	| `std.None:
-		err# = true
+		err# = `std.Some `Badint
 		-> s
 	;;
 }
 
+const failfmt = {sb, ap, opt
+	match std.vanext(ap)
+	| `Doublefmt chr:	std.sbfmt(sb, "saw duplicate format char '{}'", chr)
+	| `Badsep (e, f):	std.sbfmt(sb, "expected separator '{}', found '{}'", e, f)
+	| `Badfmt chr:		std.sbfmt(sb, "invalid format character '{}'", chr)
+	| `Badzone zone:	std.sbfmt(sb, "unknown time zone '{}'", zone)
+	| `Badname name:	std.sbfmt(sb, "could not find name '{}'", name)
+	| `Badchar:	std.sbfmt(sb, "unexpected character in parsed string")
+	| `Badampm:	std.sbfmt(sb, "invalid am/pm specifier")
+	| `Shortint:	std.sbfmt(sb, "integer had too few characters")
+	| `Badint:	std.sbfmt(sb, "integer could not be parsed")
+	;;
+}
--- a/lib/date/test/parse.myr
+++ b/lib/date/test/parse.myr
@@ -6,29 +6,36 @@
 
 	/* should be unix epoch */
 	match date.parsefmt("%Y-%m-%d %H:%M:%S %z", "1969-12-31 16:00:00 -0800")
-	| `std.Some d:
+	| `std.Ok d:
 		std.assert(d.actual == 0, "got wrong date")
 		eq(std.bfmt(buf[:], "{D}", d), "1969-12-31 16:00:00 -0800")
-	| `std.None:
-		std.fatal("failed to parse date\n")
+	| `std.Fail m:
+		std.fatal("failed to parse date: {}\n", m)
 	;;
 
+	match date.parsefmt("%Y-%m-%d", "1969 12 31")
+	| `std.Ok d:
+		std.fatal("should have errored")
+	| `std.Fail m:
+		eq(std.bfmt(buf[:], "{}", m), "expected separator '-', found ' '")
+	;;
+
 	/* parse into utc */
 	match date.parsefmtz("%Y-%m-%d %H:%M:%S %z", "1969-12-31 16:00:00 -0800", "")
-	| `std.Some d:
+	| `std.Ok d:
 		std.assert(d.actual == 0, "got wrong date")
 		eq(std.bfmt(buf[:], "{D}", d), "1970-1-01 00:00:00 +0000")
-	| `std.None:
-		std.fatal("failed to parse date\n")
+	| `std.Fail m:
+		std.fatal("failed to parse date: {}\n", m)
 	;;
 
 	/*Fri 29 Aug 2014 07:47:43 PM UTC*/
 	match date.parsefmt("%Y-%m-%d %z", "1932-10-23 +0500")
-	| `std.Some d:
+	| `std.Ok d:
 		std.assert(d.actual ==  -1173675600 * 1_000_000, "wrong timestamp")
 		eq(std.bfmt(buf[:], "{D}", d), "1932-10-23 00:00:00 +0500")
-	| `std.None:
-		std.fatal("Failed to parse date")
+	| `std.Fail m:
+		std.fatal("Failed to parse date: {}", m)
 	;;
 }