summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorAdrian Thurston <thurston@complang.org>2010-09-26 05:40:19 +0000
committerAdrian Thurston <thurston@complang.org>2010-09-26 05:40:19 +0000
commit389013e96cc1ee73fb9c5a4be1b71a131ac6cb14 (patch)
tree8aa7708b9976192592248bfd4b81202e9046a6d3 /examples
parentd625237f36b570420d102dd09c4a2fd1d5e25cb4 (diff)
downloadragel-389013e96cc1ee73fb9c5a4be1b71a131ac6cb14.tar.gz
Merged in the GO patch.
Diffstat (limited to 'examples')
-rw-r--r--examples/go/Makefile40
-rw-r--r--examples/go/README42
-rw-r--r--examples/go/atoi.rl91
-rw-r--r--examples/go/rpn.rl160
-rw-r--r--examples/go/url.rl416
-rw-r--r--examples/go/url_authority.rl165
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)))
+}