code: purgatorio

ref: d3da2e1b89f30f404c3d11053680098f1b7bf677
dir: /appl/lib/format.b/

View raw version
implement Format;
include "sys.m";
	sys: Sys;
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;
include "sexprs.m";
	sexprs: Sexprs;
	Sexp: import sexprs;
include "format.m";

# possible addition?
# se2spec(se: list of ref Sexp): (array of Fmtspec, string)

init()
{
	sys = load Sys Sys->PATH;
	sexprs = load Sexprs Sexprs->PATH;
	sexprs->init();
	bufio = load Bufio Bufio->PATH;
}

spec2se(spec: array of Fmtspec): list of ref Sexp
{
	l: list of ref Sexp;
	for(i := len spec - 1; i >= 0; i--){
		if((sp := spec[i]).fields != nil)
			l = ref Sexp.List(ref Sexp.String(sp.name, nil) :: spec2se(sp.fields)) :: l;
		else if(sp.name != nil)
			l = ref Sexp.String(sp.name, nil) :: l;
	}
	return l;
}

spec2fmt(specs: array of Fmtspec): array of Fmt
{
	if(specs == nil)
		return nil;
	f := array[len specs] of Fmt;
	for(i := 0; i < len specs; i++){
		if(specs[i].name == nil)
			f[i].kind = -1;
		else
			f[i] = (i, spec2fmt(specs[i].fields));
	}
	return f;
}


se2fmt(spec: array of Fmtspec, se: ref Sexp): (array of Fmt, string)
{
	if(!se.islist())
		return (nil, "format must be a list");
	return ses2fmt(spec, se.els());
}

ses2fmt(spec: array of Fmtspec, els: list of ref Sexp): (array of Fmt, string)
{
	a := array[len els] of Fmt;
	for(i := 0; els != nil; els = tl els){
		name := (hd els).op();
		for(j := 0; j < len spec; j++)
			if(spec[j].name == name)
				break;
		if(j == len spec)
			return (nil, sys->sprint("format name %#q not found", name));
		sp := spec[j];
		if((hd els).islist() == 0)
			a[i++] = Fmt(j, spec2fmt(sp.fields));
		else if(sp.fields == nil)
			return (nil, sys->sprint("unexpected list %#q", name));
		else{
			(f, err) := ses2fmt(sp.fields, (hd els).args());
			if(f == nil)
				return (nil, err);
			a[i++] = Fmt(j, f);
		}
	}
	return (a, nil);
}

rec2val(spec: array of Fmtspec, se: ref Sexprs->Sexp): (array of Fmtval, string)
{
	if(se.islist() == 0)
		return (nil, "expected list of fields; got "+se.text());
	els := se.els();
	if(len els > len spec)
		return (nil, sys->sprint("too many fields found, expected %d, got %s", len spec, se.text()));
	a := array[len spec] of Fmtval;
	err: string;
	for(i := 0; i < len spec; i++){
		f := spec[i];
		if(f.name == nil)
			continue;
		if(els == nil)
			return (nil, sys->sprint("too few fields found, expected %d, got %s", len spec, se.text()));
		el := hd els;
		if(f.fields == nil)
			a[i].val = el;
		else{
			if(el.islist() == 0)
				return (nil, "expected list of elements; got "+el.text());
			vl := el.els();
			a[i].recs = recs := array[len vl] of array of Fmtval;
			for(j := 0; vl != nil; vl = tl vl){
				(recs[j++], err) = rec2val(spec[i].fields, hd vl);
				if(err != nil)
					return (nil, err);
			}
		}
		els = tl els;
	}
	return (a, nil);
}

Fmtval.text(v: self Fmtval): string
{
	return v.val.astext();
}			

Fmtfile.new(spec: array of Fmtspec): Fmtfile
{
	return (spec, (ref Sexp.List(spec2se(spec))).pack());
}

Fmtfile.open(f: self Fmtfile, name: string): ref Bufio->Iobuf
{
	fd := sys->open(name, Sys->ORDWR);
	if(fd == nil){
		sys->werrstr(sys->sprint("open failed: %r"));
		return nil;
	}
	if(sys->write(fd, f.descr, len f.descr) == -1){
		sys->werrstr(sys->sprint("format write failed: %r"));
		return nil;
	}
	sys->seek(fd, big 0, Sys->SEEKSTART);
	return bufio->fopen(fd, Sys->OREAD);
}

Fmtfile.read(f: self Fmtfile, iob: ref Iobuf): (array of Fmtval, string)
{
	(se, err) := Sexp.read(iob);
	if(se == nil)
		return (nil, err);
	return rec2val(f.spec, se);
}