ref: 00c219c7d9c2b9f60c2db0e1ba7289b2301209a7
dir: /appl/lib/w3c/xpointers.b/
implement Xpointers; # # Copyright © 2005 Vita Nuova Holdings Limited # include "sys.m"; sys: Sys; include "xpointers.m"; init() { sys = load Sys Sys->PATH; } # # XPointer framework syntax # # Pointer ::= Shorthand | SchemeBased # Shorthand ::= NCName # from [XML-Names] # SchemeBased ::= PointerPart (S? PointerPart)* # PointerPart ::= SchemeName '(' SchemeData ')' # SchemeName ::= QName # from [XML-Names] # SchemeData ::= EscapedData* # EscapedData ::= NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')' # NormalChar ::= UnicodeChar - [()^] # UnicodeChar ::= [#x0 - #x10FFFF] framework(s: string): (string, list of (string, string, string), string) { (q, nm, i) := name(s, 0); if(i >= len s){ # Shorthand if(q != nil) return (nil, nil, "shorthand pointer must be unqualified name"); if(nm == nil) return (nil, nil, "missing pointer name"); return (nm, nil, nil); } # must be SchemeBased l: list of (string, string, string); for(;;){ if(nm == nil){ if(q != nil) return (nil, nil, sys->sprint("prefix but no local part in name at %d", i)); return (nil, nil, sys->sprint("expected name at %d", i)); } if(i >= len s || s[i] != '(') return (nil, nil, sys->sprint("expected '(' at %d", i)); o := i++; a := ""; nesting := 0; for(; i < len s && ((c := s[i]) != ')' || nesting); i++){ case c { '^' => if(i+1 >= len s) return (nil, nil, "unexpected eof after ^"); c = s[++i]; if(c != '(' && c != ')' && c != '^') return (nil, nil, sys->sprint("invalid escape ^%c at %d", c, i)); '(' => nesting++; ')' => if(--nesting < 0) return (nil, nil, sys->sprint("unbalanced ) at %d", i)); } a[len a] = c; } if(i >= len s) return (nil, nil, sys->sprint("unbalanced ( at %d", o)); l = (q, nm, a) :: l; if(++i == len s) break; while(i < len s && isspace(s[i])) i++; (q, nm, i) = name(s, i); } rl: list of (string, string, string); for(; l != nil; l = tl l) rl = hd l :: rl; return (nil, rl, nil); } isspace(c: int): int { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; } # # QName ::= (Prefix ':')? LocalPart # Prefix ::= NCName # LocalPart ::= NCName # #NCName :: (Oetter | '_') NCNameChar* #NCNameChar :: Oetter | Digit | '.' | '-' | '_' | CombiningChar | Extender name(s: string, o: int): (string, string, int) { (ns, i) := ncname(s, o); if(i >= len s || s[i] != ':') return (nil, ns, i); (nm, j) := ncname(s, i+1); if(j == i+1) return (nil, ns, i); # assume it's a LocalPart followed by ':' return (ns, nm, j); } ncname(s: string, o: int): (string, int) { if(o >= len s || !isalnum(c := s[o]) && c != '_' || c >= '0' && c <= '9') return (nil, o); # missing or invalid start character for(i := o; i < len s && isnamec(s[i]); i++) ; return (s[o:i], i); } isnamec(c: int): int { return isalnum(c) || c == '_' || c == '-' || c == '.'; } isalnum(c: int): int { # # Hard to get absolutely right without silly amount of character data. # Use what we know about ASCII # and assume anything above the Oatin control characters is # potentially an alphanumeric. # if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9') return 1; # usual case if(c <= ' ') return 0; if(c > 16rA0) return 1; # non-ASCII return 0; } # schemes: xpointer(), xmlns(), element() # xmlns() # XmlnsSchemeData ::= NCName S? '=' S? EscapedNamespaceName # EscapedNamespaceName ::= EscapedData* xmlns(s: string): (string, string, string) { (nm, i) := ncname(s, 0); if(nm == nil) return (nil, nil, "illegal namespace name"); while(i < len s && isspace(s[i])) i++; if(i >= len s || s[i++] != '=') return (nil, nil, "illegal xmlns declaration"); while(i < len s && isspace(s[i])) i++; return (nm, s[i:], nil); } # element() # ElementSchemeData ::= (NCName ChildSequence?) | ChildSequence # ChildSequence ::= ('/' [1-9] [0-9]*)+ element(s: string): (string, list of int, string) { nm: string; i := 0; if(s != nil && s[0] != '/'){ (nm, i) = ncname(s, 0); if(nm == nil) return (nil, nil, "illegal element name"); } l: list of int; do{ if(i >= len s || s[i++] != '/') return (nil, nil, "illegal child sequence (expected '/')"); v := 0; do{ if(i >= len s || !isdigit(s[i])) return (nil, nil, "illegal child sequence (expected integer)"); v = v*10 + s[i]-'0'; }while(++i < len s && s[i] != '/'); l = v :: l; }while(i < len s); rl: list of int; for(; l != nil; l = tl l) rl = hd l :: rl; return (nm, rl, nil); } # xpointer() # XpointerSchemeData ::= Expr # from Xpath, with new functions and data types xpointer(s: string): (ref Xpath, string) { p := ref Parse(ref Rd(s, 0, 0), nil); { e := expr(p, 0); if(p.r.i < len s) synerr("missing operator"); return (e, nil); }exception e{ "syntax error*" => return (nil, e); * => raise; } } Lerror, Ldslash, Lint, Lreal, Llit, Lvar, Ldotdot, Lop, Laxis, Lfn: con 'a'+iota; # internal lexical items Keywd: adt { name: string; val: int; }; axes: array of Keywd = array[] of { ("ancestor", Aancestor), ("ancestor-or-self", Aancestor_or_self), ("attribute", Aattribute), ("child", Achild), ("descendant", Adescendant), ("descendant-or-self", Adescendant_or_self), ("following", Afollowing), ("following-sibling", Afollowing_sibling), ("namespace", Anamespace), ("parent", Aparent), ("preceding", Apreceding), ("preceding-sibling", Apreceding_sibling), ("self", Aself), }; keywds: array of Keywd = array[] of { ("and", Oand), ("comment", Onodetype), ("div", Odiv), ("mod", Omod), ("node", Onodetype), ("or", Oor), ("processing-instruction", Onodetype), ("text", Onodetype), }; iskeywd(s: string): int { return look(keywds, s); } look(k: array of Keywd, s: string): int { for(i := 0; i < len k; i++) if(k[i].name == s) return k[i].val; return 0; } lookname(k: array of Keywd, v: int): string { for(i := 0; i < len k; i++) if(k[i].val == v) return k[i].name; return nil; } prectab := array[] of { array[] of {Oor}, array[] of {Oand}, array[] of {'=', One}, array[] of {'<', Ole, '>', Oge}, array[] of {'+', '-'}, array[] of {Omul, Odiv, Omod}, array[] of {Oneg}, # unary '-' array[] of {'|'}, # UnionExpr }; isop(t: int, p: array of int): int { if(t >= 0) for(j := 0; j < len p; j++) if(t == p[j]) return 1; return 0; } # Expr ::= OrExpr # UnionExpr ::= PathExpr | UnionExpr '|' PathExpr # PathExpr ::= LocationPath | FilterExpr | FilterExpr '/' RelativeLocationPath | # FilterExpr '//' RelativeLocationPath # OrExpr ::= AndExpr | OrExpr 'or' AndExpr # AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr # EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr | EqualityExpr '!=' RelationalExpr # RelationalExpr ::= AdditiveExpr | RelationalExpr '<' AdditiveExpr | RelationalExpr '>' AdditiveExpr | # RelationalExpr '<=' AdditiveExpr | RelationalExpr '>=' AdditiveExpr # AdditiveExpr ::= MultiplicativeExpr | AdditiveExpr '+' MultiplicativeExpr | AdditiveExpr '-' MultiplicativeExpr # MultiplicativeExpr ::= UnaryExpr | MultiplicativeExpr MultiplyOperator UnaryExpr | # MultiplicativeExpr 'div' UnaryExpr | MultiplicativeExpr 'mod' UnaryExpr # UnaryExpr ::= UnionExpr | '-' UnaryExpr expr(p: ref Parse, k: int): ref Xpath { if(k >= len prectab) return pathexpr(p); if(prectab[k][0] == Oneg){ # unary '-' if(p.look() == '-'){ p.get(); return ref Xpath.E(Oneg, expr(p,k+1), nil); } # must be UnionExpr k++; } e := expr(p, k+1); while(isop(p.look(), prectab[k])){ o := p.get().t0; e = ref Xpath.E(o, e, expr(p, k+1)); # +assoc[k] } return e; } # PathExpr ::= LocationPath | FilterExpr ( ('/' | '//') RelativeLocationPath ) # FilterExpr ::= PrimaryExpr | FilterExpr Predicate => PrimaryExpr Predicate* pathexpr(p: ref Parse): ref Xpath { # LocationPath? case p.look() { '.' or Ldotdot or Laxis or '@' or Onametest or Onodetype or '*' => return locationpath(p, 0); '/' or Ldslash => return locationpath(p, 1); } # FilterExpr e := primary(p); while(p.look() == '[') e = ref Xpath.E(Ofilter, e, predicate(p)); if((o := p.look()) == '/' || o == Ldslash) e = ref Xpath.E(Opath, e, locationpath(p, 0)); return e; } # LocationPath ::= RelativeLocationPath | AbsoluteLocationPath # AbsoluteLocationPath ::= '/' RelativeLocationPath? | AbbreviatedAbsoluteLocationPath # RelativeLocationPath ::= Step | RelativeLocationPath '/' Step # AbbreviatedAbsoluteLocationPath ::= '//' RelativeLocationPath # AbbreviatedRelativeLocationPath ::= RelativeLocationPath '//' Step locationpath(p: ref Parse, abs: int): ref Xpath { # // => /descendent-or-self::node()/ pl: list of ref Xstep; o := p.look(); if(o != '/' && o != Ldslash){ s := step(p); if(s == nil) synerr("expected Step in LocationPath"); pl = s :: pl; } while((o = p.look()) == '/' || o == Ldslash){ p.get(); if(o == Ldslash) pl = ref Xstep(Adescendant_or_self, Onodetype, nil, "node", nil, nil) :: pl; s := step(p); if(s == nil){ if(abs && pl == nil) break; # it's just an initial '/' synerr("expected Step in LocationPath"); } pl = s :: pl; } return ref Xpath.Path(abs, rev(pl)); } # Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep # AxisSpecifier ::= AxisName '::' | AbbreviatedAxisSpecifier # AxisName := ... # long list # NodeTest ::= NameTest | NodeType '(' ')' # Predicate ::= '[' PredicateExpr ']' # PredicateExpr ::= Expr # AbbreviatedStep ::= '.' | '..' # AbbreviatedAxisSpecifier ::= '@'? step(p: ref Parse): ref Xstep { # AxisSpecifier ... | AbbreviatedStep (o, ns, nm) := p.get(); axis := Achild; case o { '.' => return ref Xstep(Aself, Onodetype, nil, "node", nil, nil); # self::node() Ldotdot => return ref Xstep(Aparent, Onodetype, nil, "node", nil, nil); # parent::node() Laxis => axis = look(axes, ns); (o, ns, nm) = p.get(); '@' => axis = Aattribute; (o, ns, nm) = p.get(); * => ; } if(o == '*'){ o = Onametest; nm = "*"; ns = nil; } # NodeTest ::= NameTest | NodeType '(' ')' if(o != Onametest && o != Onodetype){ p.unget((o, ns, nm)); return nil; } arg: string; if(o == Onodetype){ # '(' ... ')' expect(p, '('); # grammar is wrong: processing-instruction can have optional literal if(nm == "processing-instruction" && p.look() == Llit) arg = p.get().t1; expect(p, ')'); } # Predicate* pl: list of ref Xpath; while((pe := predicate(p)) != nil) pl = pe :: pl; return ref Xstep(axis, o, ns, nm, arg, rev(pl)); } # PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall # FunctionCall ::= FunctionName '(' (Argument ( ',' Argument)*)? ')' # Argument ::= Expr primary(p: ref Parse): ref Xpath { (o, ns, nm) := p.get(); case o { Lvar => return ref Xpath.Var(ns, nm); '(' => e := expr(p, 0); expect(p, ')'); return e; Llit => return ref Xpath.Str(ns); Lint => return ref Xpath.Int(big ns); Lreal => return ref Xpath.Real(real ns); Lfn => expect(p, '('); al: list of ref Xpath; if(p.look() != ')'){ for(;;){ al = expr(p, 0) :: al; if(p.look() != ',') break; p.get(); } al = rev(al); } expect(p, ')'); return ref Xpath.Fn(ns, nm, al); * => synerr("invalid PrimaryExpr"); return nil; } } # Predicate ::= '[' PredicateExpr ']' # PredicateExpr ::= Expr predicate(p: ref Parse): ref Xpath { l := p.get(); if(l.t0 != '['){ p.unget(l); return nil; } e := expr(p, 0); expect(p, ']'); return e; } expect(p: ref Parse, t: int) { l := p.get(); if(l.t0 != t) synerr(sys->sprint("expected '%c'", t)); } Xpath.text(e: self ref Xpath): string { if(e == nil) return "nil"; pick r := e { E => if(r.r == nil) return sys->sprint("(%s%s)", opname(r.op), r.l.text()); if(r.op == Ofilter) return sys->sprint("%s[%s]", r.l.text(), r.r.text()); return sys->sprint("(%s%s%s)", r.l.text(), opname(r.op), r.r.text()); Fn => a := ""; for(l := r.args; l != nil; l = tl l) a += sys->sprint(",%s", (hd l).text()); if(a != "") a = a[1:]; return sys->sprint("%s(%s)", qual(r.ns, r.name), a); Var => return sys->sprint("$%s", qual(r.ns, r.name)); Path => if(r.abs) t := "/"; else t = ""; for(l := r.steps; l != nil; l = tl l){ if(t != nil && t != "/") t += "/"; t += (hd l).text(); } return t; Int => return sys->sprint("%bd", r.val); Real => return sys->sprint("%g", r.val); Str => return sys->sprint("%s", str(r.s)); } } qual(ns: string, nm: string): string { if(ns != nil) return ns+":"+nm; return nm; } str(s: string): string { for(i := 0; i < len s; i++) if(s[i] == '\'') return sys->sprint("\"%s\"", s); return sys->sprint("'%s'", s); } opname(o: int): string { case o { One => return "!="; Ole => return "<="; Oge => return ">="; Omul => return "*"; Odiv => return " div "; Omod => return " mod "; Oand => return " and "; Oor => return " or "; Oneg => return "-"; Ofilter => return " op_filter "; Opath => return "/"; * => return sys->sprint(" %c ", o); } } Xstep.text(s: self ref Xstep): string { t := sys->sprint("%s::", Xstep.axisname(s.axis)); case s.op { Onametest => if(s.ns == "*" && s.name == "*") t += "*"; else t += qual(s.ns, s.name); Onodetype => if(s.arg != nil) t += sys->sprint("%s(%s)", s.name, str(s.arg)); else t += sys->sprint("%s()", s.name); } for(l := s.preds; l != nil; l = tl l) t += sys->sprint("[%s]", (hd l).text()); return t; } Xstep.axisname(n: int): string { return lookname(axes, n); } # ExprToken ::= '(' | ')' | '[' | ']' | '.' | '..' | '@' | ',' | '::' | # NameTest | NodeType | Operator | FunctionName | AxisName | # Literal | Number | VariableReference # Operator ::= OperatorName | MultiplyOperator | '/' | '//' | '|' | '+' | '' | '=' | '!=' | '<' | '<=' | '>' | '>=' # MultiplyOperator ::= '*' # FunctionName ::= QName - NodeType # VariableReference ::= '$' QName # NameTest ::= '*' | NCName ':' '*' | QName # NodeType ::= 'comment' | 'text' | 'processing-instruction' | 'node' # Lex: type (int, string, string); Parse: adt { r: ref Rd; pb: list of Lex; # push back look: fn(p: self ref Parse): int; get: fn(p: self ref Parse): Lex; unget: fn(p: self ref Parse, t: Lex); }; Parse.get(p: self ref Parse): Lex { if(p.pb != nil){ h := hd p.pb; p.pb = tl p.pb; return h; } return lex(p.r); } Parse.look(p: self ref Parse): int { t := p.get(); p.unget(t); return t.t0; } Parse.unget(p: self ref Parse, t: Lex) { p.pb = t :: p.pb; } lex(r: ref Rd): Lex { l := lex0(r); r.prev = l.t0; return l; } # disambiguating rules are D1 to D3 # D1. preceding token p && p not in {'@', '::', '(', '[', ',', Operator} then '*' is MultiplyOperator # and NCName must be OperatorName xop(t: int): int { case t { -1 or 0 or '@' or '(' or '[' or ',' or Lop or Omul or '/' or Ldslash or '|' or '+' or '-' or '=' or One or '<' or Ole or '>' or Oge or Oand or Oor or Omod or Odiv or Laxis => return 0; } return 1; } # UnaryExpr ::= UnionExpr | '-' UnaryExpr # ExprToken ::= ... | # NameTest | NodeType | Operator | FunctionName | AxisName | # Literal | Number | VariableReference # Operator ::= OperatorName | MultiplyOperator | '/' | '//' | '|' | '+' | '' | '=' | '!=' | '<' | '<=' | '>' | '>=' # MultiplyOperator ::= '*' lex0(r: ref Rd): Lex { while(isspace(r.look())) r.get(); case c := r.get() { -1 or '(' or ')' or '[' or ']' or '@' or ',' or '+' or '-' or '|' or '=' or ':' => # singletons ('::' only valid after name, see below) return (c, nil, nil); '/' => return subseq(r, '/', Ldslash, '/'); '!' => return subseq(r, '=', One, '!'); '<' => return subseq(r, '=', Ole, '<'); '>' => return subseq(r, '=', Oge, '>'); '*' => if(xop(r.prev)) return (Omul, nil, nil); return (c, nil, nil); '.' => case r.look() { '0' to '9' => (v, nil) := number(r, r.get()); return (Lreal, v, nil); '.' => r.get(); return (Ldotdot, nil, nil); * => return ('.', nil, nil); } '$' => # variable reference (ns, nm, i) := name(r.s, r.i); if(ns == nil && nm == nil) return (Lerror, nil, nil); r.i = i; return (Lvar, ns, nm); '0' to '9' => (v, f) := number(r, c); if(f) return (Lreal, v, nil); return (Lint, v, nil); '"' or '\'' => return (Llit, literal(r, c), nil); * => if(isalnum(c) || c == '_'){ # QName/NCName r.unget(); (ns, nm, i) := name(r.s, r.i); if(ns == nil && nm == nil) return (Lerror, nil, nil); r.i = i; if(xop(r.prev)){ if(ns == nil){ o := iskeywd(nm); if(o != Laxis && o != Onodetype) return (o, nil, nil); } return (Lop, ns, nm); } while(isspace(r.look())) r.get(); case r.look() { '(' => # D2: NCName '(' =>NodeType or FunctionName if(ns == nil && iskeywd(nm) == Onodetype) return (Onodetype, nil, nm); return (Lfn, ns, nm); # possibly NodeTest ':' => # D3: NCName '::' => AxisName r.get(); case r.look() { ':' => if(ns == nil && look(axes, nm) != 0){ r.get(); return (Laxis, nm, nil); } '*' => # NameTest ::= ... | NCName ':' '*' if(ns == nil){ r.get(); return (Onametest, nm, "*"); } } r.unget(); # put back the ':' # NameTest ::= '*' | NCName ':' '*' | QName } return (Onametest, ns, nm); # actually NameTest } # unexpected character } return (Lerror, nil, nil); } subseq(r: ref Rd, a: int, t: int, e: int): Lex { if(r.look() != a) return (e, nil, nil); r.get(); return (t, nil, nil); } # Literal ::= '"'[^"]*'"' | "'"[^']* "'" literal(r: ref Rd, delim: int): string { s: string; while((c := r.get()) != delim){ if(c < 0){ synerr("missing string terminator"); return s; } if(c) s[len s] = c; # could slice r.s } return s; } # # Number ::= Digits('.' Digits?)? | '.' Digits # Digits ::= [0-9]+ # number(r: ref Rd, c: int): (string, int) { s: string; for(; isdigit(c); c = r.get()) s[len s] = c; if(c != '.'){ if(c >= 0) r.unget(); return (s, 0); } if(!isdigit(c = r.get())){ if(c >= 0) r.unget(); r.unget(); # the '.' return (s, 0); } s[len s] = '.'; do{ s[len s] = c; }while(isdigit(c = r.get())); if(c >= 0) r.unget(); return (s, 1); } isdigit(c: int): int { return c>='0' && c<='9'; } Rd: adt{ s: string; i: int; prev: int; # previous token get: fn(r: self ref Rd): int; look: fn(r: self ref Rd): int; unget: fn(r: self ref Rd); }; Rd.get(r: self ref Rd): int { if(r.i >= len r.s) return -1; return r.s[r.i++]; } Rd.look(r: self ref Rd): int { if(r.i >= len r.s) return -1; return r.s[r.i]; } Rd.unget(r: self ref Rd) { if(r.i > 0) r.i--; } rev[T](l: list of T): list of T { rl: list of T; for(; l != nil; l = tl l) rl = hd l :: rl; return rl; } synerr(s: string) { raise "syntax error: "+s; } # to do: # dictionary?