diff options
author | Adrian Thurston <thurston@complang.org> | 2010-09-26 05:40:19 +0000 |
---|---|---|
committer | Adrian Thurston <thurston@complang.org> | 2010-09-26 05:40:19 +0000 |
commit | 389013e96cc1ee73fb9c5a4be1b71a131ac6cb14 (patch) | |
tree | 8aa7708b9976192592248bfd4b81202e9046a6d3 /examples | |
parent | d625237f36b570420d102dd09c4a2fd1d5e25cb4 (diff) | |
download | ragel-389013e96cc1ee73fb9c5a4be1b71a131ac6cb14.tar.gz |
Merged in the GO patch.
Diffstat (limited to 'examples')
-rw-r--r-- | examples/go/Makefile | 40 | ||||
-rw-r--r-- | examples/go/README | 42 | ||||
-rw-r--r-- | examples/go/atoi.rl | 91 | ||||
-rw-r--r-- | examples/go/rpn.rl | 160 | ||||
-rw-r--r-- | examples/go/url.rl | 416 | ||||
-rw-r--r-- | examples/go/url_authority.rl | 165 |
6 files changed, 914 insertions, 0 deletions
diff --git a/examples/go/Makefile b/examples/go/Makefile new file mode 100644 index 00000000..996d2eea --- /dev/null +++ b/examples/go/Makefile @@ -0,0 +1,40 @@ + +check: atoi rpn url + ./atoi + ./rpn + ./url + @echo PASS + +graph: atoi.png rpn.png url.png url_authority.png + evince atoi.png + evince rpn.png + evince url.png + evince url_authority.png + +atoi: atoi.6 +atoi.6: atoi.go +atoi.go: atoi.rl +atoi.dot: atoi.rl +atoi.png: atoi.dot + +rpn: rpn.6 +rpn.6: rpn.go +rpn.go: rpn.rl +rpn.dot: rpn.rl +rpn.png: rpn.dot + +url: url.6 +url.6: url.go url_authority.go +url.go: url.rl +url.dot: url.rl +url.png: url.dot +url_authority.go: url_authority.rl +url_authority.dot: url_authority.rl +url_authority.png: url_authority.dot + +clean: ; rm -f *.6 *.go *.png *.dot atoi rpn url +%: %.6 ; 6l -o $@ $^ +%.6: %.go ; 6g -o $@ $^ +%.go: %.rl ; ragel -Z -G2 -o $@ $< +%.dot: %.rl ; ragel -V -p -Z -G2 -o $@ $< +%.png: %.dot ; dot -Tpng -o $@ $< diff --git a/examples/go/README b/examples/go/README new file mode 100644 index 00000000..16272c91 --- /dev/null +++ b/examples/go/README @@ -0,0 +1,42 @@ +.. -*-rst-*- + +Ragel Examples for Go +===================== + +These examples serve the following purposes: + +- Help you learn Ragel +- Test the correctness of the code I wrote for Ragel +- Benchmark Ragel's performance on your machine +- And hopefully give you some code you can steal ;] + +To get started you should first ``make install`` ragel. Then navigate +to this directory and run:: + + make + +To automatically compile/test/benchmark these examples. If you're on +a 32-bit platform, you'll need to edit the Makefile and change all the +'6' characters to '8'. + +The following examples are provided: + +- atoi.rl: Convert string to integer (very simple) +- rpn.rl: Reverse polish notation calculator (simple) +- url.rl: Very fast and robust HTTP/SIP URL parser (very complicated) + +To see graphviz diagrams of the state machines generated by Ragel in +these examples, run the following commands:: + + sudo apt-get install graphviz evince + make graph + +Those diagrams (along with the pdf manual) are super important for +troubleshooting and simplifying your Ragel code. + +If Ragel accidentally outputs code that looks like C, it probably +means I forgot to override a function or two inside: +``ragel/goipgoto.cpp``. + +I truly hope these examples help you in your personal and professional +endeavors. If you have any questions my email is: jtunney@gmail.com diff --git a/examples/go/atoi.rl b/examples/go/atoi.rl new file mode 100644 index 00000000..58e7b615 --- /dev/null +++ b/examples/go/atoi.rl @@ -0,0 +1,91 @@ +// -*-go-*- +// +// Convert a string to an integer. +// +// To compile: +// +// ragel -Z -G2 -o atoi.go atoi.rl +// 6g -o atoi.6 atoi.go +// 6l -o atoi atoi.6 +// ./atoi +// +// To show a diagram of your state machine: +// +// ragel -V -G2 -p -o atoi.dot atoi.rl +// dot -Tpng -o atoi.png atoi.dot +// chrome atoi.png +// + +package main + +import ( + "os" + "fmt" +) + +%%{ + machine atoi; + write data; +}%% + +func atoi(data string) (val int) { + cs, p, pe := 0, 0, len(data) + neg := false + + %%{ + action see_neg { neg = true } + action add_digit { val = val * 10 + (int(fc) - '0') } + + main := + ( '-'@see_neg | '+' )? ( digit @add_digit )+ + '\n'? + ; + + write init; + write exec; + }%% + + if neg { + val = -1 * val; + } + + if cs < atoi_first_final { + fmt.Println("atoi: there was an error:", cs, "<", atoi_first_final) + fmt.Println(data) + for i := 0; i < p; i++ { + fmt.Print(" ") + } + fmt.Println("^") + } + + return val +} + +////////////////////////////////////////////////////////////////////// + +type atoiTest struct { + s string + v int +} + +var atoiTests = []atoiTest{ + atoiTest{"7", 7}, + atoiTest{"666", 666}, + atoiTest{"-666", -666}, + atoiTest{"+666", 666}, + atoiTest{"1234567890", 1234567890}, + atoiTest{"+1234567890\n", 1234567890}, + // atoiTest{"+ 1234567890", 1234567890}, // i will fail +} + +func main() { + res := 0 + for _, test := range atoiTests { + res := atoi(test.s) + if res != test.v { + fmt.Fprintf(os.Stderr, "FAIL atoi(%#v) != %#v\n", test.s, test.v) + res = 1 + } + } + os.Exit(res) +} diff --git a/examples/go/rpn.rl b/examples/go/rpn.rl new file mode 100644 index 00000000..1716ab3b --- /dev/null +++ b/examples/go/rpn.rl @@ -0,0 +1,160 @@ +// -*-go-*- +// +// Reverse Polish Notation Calculator +// Copyright (c) 2010 J.A. Roberts Tunney +// MIT License +// +// To compile: +// +// ragel -Z -G2 -o rpn.go rpn.rl +// 6g -o rpn.6 rpn.go +// 6l -o rpn rpn.6 +// ./rpn +// +// To show a diagram of your state machine: +// +// ragel -V -G2 -p -o rpn.dot rpn.rl +// dot -Tpng -o rpn.png rpn.dot +// chrome rpn.png +// + +package main + +import ( + "os" + "fmt" + "strconv" +) + +type stack struct { + items []int + count int +} + +func (s *stack) pop() int { + s.count-- + v := s.items[s.count] + return v +} + +func (s *stack) push(v int) { + s.items[s.count] = v + s.count++ +} + +func abs(v int) int { + if v < 0 { + v = -v + } + return v +} + +%% machine rpn; +%% write data; + +func rpn(data string) (res int, err os.Error) { + // p, pe, eof := 0, len(data), len(data) + cs, p, pe := 0, 0, len(data) + mark := 0 + st := &stack{items: make([]int, 128), count: 0} + + %%{ + action mark { mark = p } + action push { x, _ := strconv.Atoi(data[mark:p]); st.push(x) } + action add { y, x := st.pop(), st.pop(); st.push(x + y) } + action sub { y, x := st.pop(), st.pop(); st.push(x - y) } + action mul { y, x := st.pop(), st.pop(); st.push(x * y) } + action div { y, x := st.pop(), st.pop(); st.push(x / y) } + action abs { st.push(abs(st.pop())) } + action abba { st.push(666) } + + stuff = digit+ >mark %push + | '+' @add + | '-' @sub + | '*' @mul + | '/' @div + | 'abs' %abs + | 'add' %add + | 'abba' %abba + ; + + main := ( space | stuff space )* ; + + write init; + write exec; + }%% + + if cs < rpn_first_final { + if p == pe { + return 0, os.ErrorString("unexpected eof") + } else { + return 0, os.ErrorString(fmt.Sprintf("error at position %d", p)) + } + } + + if st.count == 0 { + return 0, os.ErrorString("rpn stack empty on result") + } + + return st.pop(), nil +} + +////////////////////////////////////////////////////////////////////// + +type rpnTest struct { + s string + v int +} + +var rpnTests = []rpnTest{ + rpnTest{"666\n", 666}, + rpnTest{"666 111\n", 111}, + rpnTest{"4 3 add\n", 7}, + rpnTest{"4 3 +\n", 7}, + rpnTest{"4 3 -\n", 1}, + rpnTest{"4 3 *\n", 12}, + rpnTest{"6 2 /\n", 3}, + rpnTest{"0 3 -\n", -3}, + rpnTest{"0 3 - abs\n", 3}, + rpnTest{" 2 2 + 3 - \n", 1}, + rpnTest{"10 7 3 2 * - +\n", 11}, + rpnTest{"abba abba add\n", 1332}, +} + +type rpnFailTest struct { + s string + e string +} + +var rpnFailTests = []rpnFailTest{ + rpnFailTest{"\n", "rpn stack empty on result"}, +} + +func main() { + rc := 0 + + for _, test := range rpnTests { + res, err := rpn(test.s) + if err != nil { + fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) %s\n", test.s, err) + rc = 1 + } else if res != test.v { + fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) -> %#v != %#v\n", + test.s, res, test.v) + rc = 1 + } + } + + for _, test := range rpnFailTests { + res, err := rpn(test.s) + if err == nil { + fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) -> %#v should fail: %#v\n", + test.s, res, test.e) + } else if err.String() != test.e { + fmt.Fprintf(os.Stderr, "FAIL rpn(%#v) %#v should be %#v\n", + test.s, err.String(), test.e) + } + } + + os.Exit(rc) +} diff --git a/examples/go/url.rl b/examples/go/url.rl new file mode 100644 index 00000000..02e28083 --- /dev/null +++ b/examples/go/url.rl @@ -0,0 +1,416 @@ +// -*-go-*- +// +// URL Parser +// Copyright (c) 2010 J.A. Roberts Tunney +// MIT License +// +// To compile: +// +// ragel -Z -G2 -o url.go url.rl +// ragel -Z -G2 -o url_authority.go url_authority.rl +// 6g -o url.6 url.go url_authority.go +// 6l -o url url.6 +// ./url +// +// To show a diagram of your state machine: +// +// ragel -V -G2 -p -o url.dot url.rl +// dot -Tpng -o url.png url.dot +// chrome url.png +// +// ragel -V -G2 -p -o url_authority.dot url_authority.rl +// dot -Tpng -o url_authority.png url_authority.dot +// chrome url_authority.png +// +// Reference: +// +// - http://tools.ietf.org/html/rfc3986 +// + +package main + +import ( + "os" + "fmt" + "time" +) + +type URL struct { + Scheme string // http, sip, file, etc. (never blank, always lowercase) + User string // who is you yo + Pass string // for like, logging in + Host string // IP 4/6 address or hostname (mandatory) + Port int // like 80 or 5060 (default 0) + Params string // stuff after ';' (NOT UNESCAPED, used in sip) + Path string // stuff starting with '/' + Query string // stuff after '?' (NOT UNESCAPED) + Fragment string // stuff after '#' +} + +%% machine url; +%% write data; + +// i parse absolute urls and don't suck at it. i'll parse just about +// any type of url you can think of and give you a human-friendly data +// structure. +// +// this routine takes no more than a few microseconds, is reentrant, +// performs in a predictable manner (for security/soft-realtime,) +// doesn't modify your `data` buffer, and under no circumstances will +// it panic (i hope!) +func URLParse(data []byte) (url *URL, err os.Error) { + cs, p, pe, eof := 0, 0, len(data), len(data) + mark := 0 + url = new(URL) + + // this buffer is so we can unescape while we roll + var hex byte + buf := make([]byte, len(data)) + amt := 0 + + %%{ + action mark { mark = p } + action str_start { amt = 0 } + action str_char { buf[amt] = fc; amt++ } + action str_lower { buf[amt] = fc + 0x20; amt++ } + action hex_hi { hex = unhex(fc) * 16 } + action hex_lo { hex += unhex(fc) + buf[amt] = hex; amt++ } + action scheme { url.Scheme = string(buf[0:amt]) } + action authority { err = url.parseAuthority(data[mark:p]) + if err != nil { return nil, err } } + action path { url.Path = string(buf[0:amt]) } + action query { url.Query = string(data[mark:p]) } + action fragment { url.Fragment = string(buf[0:amt]) } + + # # do this instead if you *actually* use URNs (lol) + # action authority { url.Authority = string(data[mark:p]) } + + # define what a single character is allowed to be + toxic = ( cntrl | 127 ) ; + scary = ( toxic | " " | "\"" | "#" | "%" | "<" | ">" ) ; + schmchars = ( lower | digit | "+" | "-" | "." ) ; + authchars = any -- ( scary | "/" | "?" | "#" ) ; + pathchars = any -- ( scary | "?" | "#" ) ; + querchars = any -- ( scary | "#" ) ; + fragchars = any -- ( scary ) ; + + # define how characters trigger actions + escape = "%" xdigit xdigit ; + unescape = "%" ( xdigit @hex_hi ) ( xdigit @hex_lo ) ; + schmfirst = ( upper @str_lower ) | ( lower @str_char ) ; + schmchar = ( upper @str_lower ) | ( schmchars @str_char ) ; + authchar = escape | authchars ; + pathchar = unescape | ( pathchars @str_char ) ; + querchar = escape | querchars ; + fragchar = unescape | ( fragchars @str_char ) ; + + # define multi-character patterns + scheme = ( schmfirst schmchar* ) >str_start %scheme ; + authority = authchar+ >mark %authority ; + path = ( ( "/" @str_char ) pathchar* ) >str_start %path ; + query = "?" ( querchar* >mark %query ) ; + fragment = "#" ( fragchar* >str_start %fragment ) ; + url = scheme ":" "//"? authority path? query? fragment? + | scheme ":" "//" authority? path? query? fragment? + ; + + main := url; + write init; + write exec; + }%% + + if cs < url_first_final { + if p == pe { + return nil, os.ErrorString( + fmt.Sprintf("unexpected eof: %s", data)) + } else { + return nil, os.ErrorString( + fmt.Sprintf("error in url at pos %d: %s", p, data)) + } + } + + return url, nil +} + +func unhex(b byte) byte { + switch { + case '0' <= b && b <= '9': + return b - '0' + case 'a' <= b && b <= 'f': + return b - 'a' + 10 + case 'A' <= b && b <= 'F': + return b - 'A' + 10 + } + return 0 +} + +////////////////////////////////////////////////////////////////////// + +type urlTest struct { + s []byte + url URL +} + +var urlTests = []urlTest{ + + urlTest{ + []byte("http://user:pass@example.com:80;hello/lol.php?fun#omg"), + URL{ + Scheme: "http", + User: "user", + Pass: "pass", + Host: "example.com", + Port: 80, + Params: "hello", + Path: "/lol.php", + Query: "fun", + Fragment: "omg", + }, + }, + + urlTest{ + []byte("a:b"), + URL{ + Scheme: "a", + Host: "b", + }, + }, + + urlTest{ + []byte("GoPHeR://@example.com@:;/?#"), + URL{ + Scheme: "gopher", + Host: "@example.com@", + Path: "/", + }, + }, + + urlTest{ + []byte("ldap://[2001:db8::7]/c=GB?objectClass/?one"), + URL{ + Scheme: "ldap", + Host: "2001:db8::7", + Path: "/c=GB", + Query: "objectClass/?one", + }, + }, + + urlTest{ + []byte("http://user@example.com"), + URL{ + Scheme: "http", + User: "user", + Host: "example.com", + }, + }, + + urlTest{ + []byte("http://品研发和研发管@☃.com:65000;%20"), + URL{ + Scheme: "http", + User: "品研发和研发管", + Host: "☃.com", + Port: 65000, + Params: "%20", + }, + }, + + urlTest{ + []byte("https://example.com:80"), + URL{ + Scheme: "https", + Host: "example.com", + Port: 80, + }, + }, + + urlTest{ + []byte("file:///etc/passwd"), + URL{ + Scheme: "file", + Path: "/etc/passwd", + }, + }, + + urlTest{ + []byte("file:///c:/WINDOWS/clock.avi"), + URL{ + Scheme: "file", + Path: "/c:/WINDOWS/clock.avi", // <-- is this kosher? + }, + }, + + urlTest{ + []byte("file://hostname/path/to/the%20file.txt"), + URL{ + Scheme: "file", + Host: "hostname", + Path: "/path/to/the file.txt", + }, + }, + + urlTest{ + []byte("sip:example.com"), + URL{ + Scheme: "sip", + Host: "example.com", + }, + }, + + urlTest{ + []byte("sip:example.com:5060"), + URL{ + Scheme: "sip", + Host: "example.com", + Port: 5060, + }, + }, + + urlTest{ + []byte("mailto:ditto@pokémon.com"), + URL{ + Scheme: "mailto", + User: "ditto", + Host: "pokémon.com", + }, + }, + + urlTest{ + []byte("sip:[dead:beef::666]:5060"), + URL{ + Scheme: "sip", + Host: "dead:beef::666", + Port: 5060, + }, + }, + + urlTest{ + []byte("tel:+12126660420"), + URL{ + Scheme: "tel", + Host: "+12126660420", + }, + }, + + urlTest{ + []byte("sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00/palfun.html?haha#omg"), + URL{ + Scheme: "sip", + User: "bob barker", + Pass: "priceisright", + Host: "dead:beef::666", + Port: 5060, + Params: "isup-oli=00", + Path: "/palfun.html", + Query: "haha", + Fragment: "omg", + }, + }, + + urlTest{ + []byte("http://www.google.com/search?%68l=en&safe=off&q=omfg&aq=f&aqi=g2g-s1g1g-s1g5&aql=&oq=&gs_rfai="), + URL{ + Scheme: "http", + Host: "www.google.com", + Path: "/search", + Query: "%68l=en&safe=off&q=omfg&aq=f&aqi=g2g-s1g1g-s1g5&aql=&oq=&gs_rfai=", + }, + }, + +} + +func (test *urlTest) compare(url *URL) (passed bool) { + if url.Scheme != test.url.Scheme { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) scheme: %#v != %#v\n", + string(test.s), url.Scheme, test.url.Scheme) + passed = true + } + if url.User != test.url.User { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) user: %#v != %#v\n", + string(test.s), url.User, test.url.User) + passed = true + } + if url.Pass != test.url.Pass { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) pass: %#v != %#v\n", + string(test.s), url.Pass, test.url.Pass) + passed = true + } + if url.Host != test.url.Host { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) host: %#v != %#v\n", + string(test.s), url.Host, test.url.Host) + passed = true + } + if url.Port != test.url.Port { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) port: %#v != %#v\n", + string(test.s), url.Port, test.url.Port) + passed = true + } + if url.Port != test.url.Port { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) port: %#v != %#v\n", + string(test.s), url.Port, test.url.Port) + passed = true + } + if url.Params != test.url.Params { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) params: %#v != %#v\n", + string(test.s), url.Params, test.url.Params) + passed = true + } + if url.Path != test.url.Path { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) path: %#v != %#v\n", + string(test.s), url.Path, test.url.Path) + passed = true + } + if url.Query != test.url.Query { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) query: %#v != %#v\n", + string(test.s), url.Query, test.url.Query) + passed = true + } + if url.Fragment != test.url.Fragment { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) fragment: %#v != %#v\n", + string(test.s), url.Fragment, test.url.Fragment) + passed = true + } + return !passed +} + +func bench() { + const rounds = 10000 + for _, s := range [][]byte{ + []byte("a:a"), + []byte("http://google.com/"), + []byte("sip:jtunney@lobstertech.com"), + []byte("http://user:pass@example.com:80;hello/lol.php?fun#omg"), + []byte("file:///etc/passwd"), + } { + ts1 := time.Nanoseconds() + for i := 0; i < rounds; i++ { + URLParse(s) + } + ts2 := time.Nanoseconds() + fmt.Printf("BENCH URLParse(%s) -> %d ns\n", s, (ts2 - ts1) / rounds) + } +} + +func test() (rc int) { + for _, test := range urlTests { + url, err := URLParse(test.s) + if err != nil { + fmt.Fprintf(os.Stderr, "FAIL url(%#v) %s\n", string(test.s), err) + rc = 1 + continue + } + if !test.compare(url) { + rc = 1 + } + } + return rc +} + +func main() { + rc := test() + if rc == 0 { + bench() + } + os.Exit(rc) +} diff --git a/examples/go/url_authority.rl b/examples/go/url_authority.rl new file mode 100644 index 00000000..17c45e83 --- /dev/null +++ b/examples/go/url_authority.rl @@ -0,0 +1,165 @@ +// -*-go-*- +// +// URL Parser +// Copyright (c) 2010 J.A. Roberts Tunney +// MIT License +// + +package main + +import ( + "os" + "fmt" + "strconv" +) + +%% machine url_authority; +%% write data; + +// i parse strings like `alice@pokémon.com`. +// +// sounds simple right? but i also parse stuff like: +// +// bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00 +// +// which in actual reality is: +// +// - User: "bob barker" +// - Pass: "priceisright" +// - Host: "dead:beef::666" +// - Port: 5060 +// - Params: "isup-oli=00" +// +// which was probably extracted from an absolute url that looked like: +// +// sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00/palfun.html?haha#omg +// +// which was probably extracted from its address form: +// +// "Bob Barker" <sip:bob%20barker:priceisright@[dead:beef::666]:5060;isup-oli=00/palfun.html?haha#omg>;tag=666 +// +// who would have thought this could be so hard ._. +func (url *URL) parseAuthority(data []byte) (err os.Error) { + cs, p, pe, eof := 0, 0, len(data), len(data) + mark := 0 + + // temporary holding place for user:pass and/or host:port cuz an + // optional term (user[:pass]) coming before a mandatory term + // (host[:pass]) would require require backtracking and all that + // evil nondeterministic stuff which ragel seems to hate. (for + // this same reason you're also allowed to use square quotes + // around the username.) + var b1, b2 string + + // this buffer is so we can unescape while we roll + var hex byte + buf := make([]byte, len(data)) + amt := 0 + + %%{ + action mark { mark = p } + action str_start { amt = 0 } + action str_char { buf[amt] = fc; amt++ } + action hex_hi { hex = unhex(fc) * 16 } + action hex_lo { hex += unhex(fc) + buf[amt] = hex; amt++ } + action copy_b1 { b1 = string(buf[0:amt]); amt = 0 } + action copy_b2 { b2 = string(buf[0:amt]); amt = 0 } + action copy_host { url.Host = string(b1); amt = 0 } + + action copy_port { + if b2 != "" { + url.Port, err = strconv.Atoi(string(b2)) + if err != nil { goto fail } + if url.Port > 65535 { goto fail } + } + } + + action params { + url.Params = string(data[mark:p]) + } + + action params_eof { + url.Params = string(data[mark:p]) + return nil + } + + action atsymbol { + url.User = string(b1) + url.Pass = string(b2) + b2 = "" + } + + action alldone { + url.Host = string(b1) + if url.Host == "" { + url.Host = string(buf[0:amt]) + } else { + if amt > 0 { + b2 = string(buf[0:amt]) + } + if b2 != "" { + url.Port, err = strconv.Atoi(string(b2)) + if err != nil { goto fail } + if url.Port > 65535 { goto fail } + } + } + return nil + } + + # define what a single character is allowed to be + toxic = ( cntrl | 127 ) ; + scary = ( toxic | space | "\"" | "#" | "%" | "<" | ">" ) ; + authdelims = ( "/" | "?" | "#" | ":" | "@" | ";" | "[" | "]" ) ; + userchars = any -- ( authdelims | scary ) ; + userchars_esc = userchars | ":" ; + passchars = userchars ; + hostchars = passchars | "@" ; + hostchars_esc = hostchars | ":" ; + portchars = digit ; + paramchars = hostchars | ":" | ";" ; + + # define how characters trigger actions + escape = "%" xdigit xdigit ; + unescape = "%" ( xdigit @hex_hi ) ( xdigit @hex_lo ) ; + userchar = unescape | ( userchars @str_char ) ; + userchar_esc = unescape | ( userchars_esc @str_char ) ; + passchar = unescape | ( passchars @str_char ) ; + hostchar = unescape | ( hostchars @str_char ) ; + hostchar_esc = unescape | ( hostchars_esc @str_char ) ; + portchar = unescape | ( portchars @str_char ) ; + paramchar = escape | paramchars ; + + # define multi-character patterns + user_plain = userchar+ >str_start %copy_b1 ; + user_quoted = "[" ( userchar_esc+ >str_start %copy_b1 ) "]" ; + user = ( user_quoted | user_plain ) %/alldone ; + pass = passchar+ >str_start %copy_b2 %/alldone ; + host_plain = hostchar+ >str_start %copy_b1 %copy_host ; + host_quoted = "[" ( hostchar_esc+ >str_start %copy_b1 %copy_host ) "]" ; + host = ( host_quoted | host_plain ) %/alldone ; + port = portchar* >str_start %copy_b2 %copy_port %/alldone ; + params = ";" ( paramchar* >mark %params %/params_eof ) ; + userpass = user ( ":" pass )? ; + hostport = host ( ":" port )? ; + authority = ( userpass ( "@" @atsymbol ) )? hostport params? ; + + main := authority; + write init; + write exec; + }%% + + // if cs >= url_authority_first_final { + // return nil + // } + +fail: + // fmt.Println("error state", cs) + // fmt.Println(string(data)) + // for i := 0; i < p; i++ { + // fmt.Print(" ") + // } + // fmt.Println("^") + // fmt.Println(url) + return os.ErrorString(fmt.Sprintf("bad url authority: %#v", string(data))) +} |