├── LICENSE ├── airlock ├── airlock.go └── airlock_test.go ├── atom ├── atom.go └── atom_test.go ├── go.mod ├── go.sum ├── hoon ├── ast │ └── ast.go ├── parser │ ├── parser.go │ └── parser_test.go ├── scanner │ ├── scanner.go │ └── scanner_test.go └── token │ └── token.go ├── mach ├── mach.go └── mach_test.go └── ob ├── ob.go └── ob_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Luke Champine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /airlock/airlock.go: -------------------------------------------------------------------------------- 1 | package airlock 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/hex" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "net/http/cookiejar" 14 | "net/url" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | 19 | "lukechampine.com/frand" 20 | ) 21 | 22 | // A Client facilitates an airlock connection to an Urbit. 23 | type Client struct { 24 | addr string 25 | http http.Client 26 | cond *sync.Cond // for waking goroutines waiting on SSE acks 27 | once sync.Once // for spawning sseLoop 28 | 29 | mu sync.Mutex 30 | nextID int 31 | lastSeenID int 32 | lastAckedID int 33 | subs map[int]chan json.RawMessage 34 | acks map[int]error 35 | sseErr error 36 | } 37 | 38 | func (c *Client) nextEventID() int { 39 | c.mu.Lock() 40 | defer c.mu.Unlock() 41 | c.nextID++ 42 | return c.nextID 43 | } 44 | 45 | func (c *Client) sseLoop() error { 46 | req, _ := http.NewRequest("GET", c.addr, nil) 47 | req.Header.Set("Accept", "text/event-stream") 48 | resp, err := c.http.Do(req) 49 | if err != nil { 50 | return fmt.Errorf("couldn't initiate SSE connection: %w", err) 51 | } 52 | defer resp.Body.Close() 53 | s := bufio.NewScanner(resp.Body) 54 | for s.Scan() { 55 | line := s.Bytes() 56 | switch { 57 | case bytes.HasPrefix(line, []byte("id: ")): 58 | id, err := strconv.Atoi(string(line[4:])) 59 | if err != nil { 60 | return fmt.Errorf("couldn't parse SSE ID: %w", err) 61 | } 62 | c.mu.Lock() 63 | c.lastSeenID = id 64 | c.mu.Unlock() 65 | case bytes.HasPrefix(line, []byte("data: ")): 66 | var data struct { 67 | ID int 68 | Response string 69 | OK string 70 | Err string 71 | JSON json.RawMessage 72 | } 73 | if err := json.Unmarshal(line[6:], &data); err != nil { 74 | return fmt.Errorf("couldn't parse SSE data: %w", err) 75 | } 76 | switch data.Response { 77 | case "subscribe", "poke": 78 | c.mu.Lock() 79 | if data.Err == "" { 80 | c.acks[data.ID] = nil 81 | } else { 82 | c.acks[data.ID] = errors.New(data.Err) 83 | } 84 | c.mu.Unlock() 85 | c.cond.Broadcast() 86 | case "diff": 87 | c.mu.Lock() 88 | if s, ok := c.subs[data.ID]; ok { 89 | s <- data.JSON 90 | } 91 | c.mu.Unlock() 92 | case "quit": 93 | c.mu.Lock() 94 | if s, ok := c.subs[data.ID]; ok { 95 | close(s) 96 | delete(c.subs, data.ID) 97 | } 98 | c.mu.Unlock() 99 | } 100 | } 101 | } 102 | return s.Err() 103 | } 104 | 105 | func (c *Client) waitForAck(id int) error { 106 | c.cond.L.Lock() 107 | defer c.cond.L.Unlock() 108 | for { 109 | if c.sseErr != nil { 110 | return c.sseErr 111 | } 112 | if err, ok := c.acks[id]; ok { 113 | delete(c.acks, id) 114 | return err 115 | } 116 | c.cond.Wait() 117 | } 118 | } 119 | 120 | func (c *Client) sendJSONToChannel(v ...interface{}) error { 121 | // include ack if necessary 122 | c.mu.Lock() 123 | lastAcked, lastSeen := c.lastAckedID, c.lastSeenID 124 | c.mu.Unlock() 125 | if lastAcked != lastSeen { 126 | // the ack MUST come before other messages; if the ack comes after a 127 | // delete, eyre will get mad at us 128 | v = append([]interface{}{struct { 129 | Action string `json:"action"` 130 | EventID int `json:"event-id"` 131 | }{"ack", lastSeen}}, v...) 132 | } 133 | 134 | js, _ := json.Marshal(v) 135 | req, _ := http.NewRequest("PUT", c.addr, bytes.NewReader(js)) 136 | req.Header.Set("Content-Type", "application/json") 137 | resp, err := c.http.Do(req) 138 | if err != nil { 139 | return err 140 | } 141 | defer io.Copy(ioutil.Discard, resp.Body) 142 | defer resp.Body.Close() 143 | if resp.StatusCode != 204 { 144 | switch resp.StatusCode { 145 | case http.StatusBadRequest: 146 | return errors.New("invalid request") 147 | case http.StatusForbidden: 148 | return errors.New("unauthenticated request (expired cookie?)") 149 | default: 150 | return fmt.Errorf("HTTP status code %v (%v)", resp.StatusCode, http.StatusText(resp.StatusCode)) 151 | } 152 | } 153 | 154 | c.mu.Lock() 155 | c.lastAckedID = lastSeen 156 | c.mu.Unlock() 157 | 158 | // initiate SSE connection (if not already connected) 159 | c.once.Do(func() { 160 | go func() { 161 | if err := c.sseLoop(); err != nil { 162 | c.mu.Lock() 163 | c.sseErr = err 164 | c.mu.Unlock() 165 | c.cond.Broadcast() 166 | } 167 | }() 168 | }) 169 | return nil 170 | } 171 | 172 | // Poke sends a poke and waits for it to be acknowledged. 173 | func (c *Client) Poke(ship, app, mark string, v interface{}) error { 174 | id := c.nextEventID() 175 | err := c.sendJSONToChannel(struct { 176 | ID int `json:"id"` 177 | Action string `json:"action"` 178 | Ship string `json:"ship"` 179 | App string `json:"app"` 180 | Mark string `json:"mark"` 181 | JSON interface{} `json:"json"` 182 | }{id, "poke", ship, app, mark, v}) 183 | if err != nil { 184 | return err 185 | } 186 | return c.waitForAck(id) 187 | } 188 | 189 | // Subscribe sets up a subscription on the specified path. 190 | func (c *Client) Subscribe(ship, app, path string) (*Subscription, error) { 191 | id := c.nextEventID() 192 | eventCh := make(chan json.RawMessage, 1) 193 | c.mu.Lock() 194 | c.subs[id] = eventCh 195 | c.mu.Unlock() 196 | err := c.sendJSONToChannel(struct { 197 | ID int `json:"id"` 198 | Action string `json:"action"` 199 | Ship string `json:"ship"` 200 | App string `json:"app"` 201 | Path string `json:"path"` 202 | }{id, "subscribe", ship, app, path}) 203 | if err != nil { 204 | return nil, err 205 | } 206 | if err := c.waitForAck(id); err != nil { 207 | return nil, err 208 | } 209 | return &Subscription{ 210 | c: c, 211 | id: id, 212 | Events: eventCh, 213 | }, nil 214 | } 215 | 216 | // Delete deletes the airlock channel. 217 | func (c *Client) Delete() error { 218 | return c.sendJSONToChannel(struct { 219 | ID int `json:"id"` 220 | Action string `json:"action"` 221 | }{c.nextEventID(), "delete"}) 222 | } 223 | 224 | // NewClient connects to the Urbit listening on the specified address. 225 | func NewClient(addr, code string) (*Client, error) { 226 | c := &Client{ 227 | addr: fmt.Sprintf("%v/~/channel/go-airlock-%v", addr, hex.EncodeToString(frand.Bytes(6))), 228 | acks: make(map[int]error), 229 | subs: make(map[int]chan json.RawMessage), 230 | } 231 | c.cond = sync.NewCond(&c.mu) 232 | 233 | resp, err := c.http.Post(fmt.Sprintf("%v/~/login", addr), "application/x-www-form-urlencoded", strings.NewReader("password="+code)) 234 | if err != nil { 235 | return nil, err 236 | } 237 | defer io.Copy(ioutil.Discard, resp.Body) 238 | defer resp.Body.Close() 239 | if resp.StatusCode != 204 { 240 | return nil, fmt.Errorf("ship returned HTTP status code %v (%v)", resp.StatusCode, http.StatusText(resp.StatusCode)) 241 | } 242 | c.http.Jar, err = cookiejar.New(nil) 243 | if err != nil { 244 | return nil, err 245 | } 246 | u, _ := url.Parse(addr) 247 | c.http.Jar.SetCookies(u, resp.Cookies()) 248 | return c, nil 249 | } 250 | 251 | // A Subscription is an active subscription. 252 | type Subscription struct { 253 | c *Client 254 | id int 255 | Events <-chan json.RawMessage 256 | } 257 | 258 | // Unsubscribe unsubscribes from the subscription. 259 | func (s *Subscription) Unsubscribe() error { 260 | // NOTE: we do not wait for acknowledgement here 261 | err := s.c.sendJSONToChannel(struct { 262 | ID int `json:"id"` 263 | Action string `json:"action"` 264 | Subscription int `json:"subscription"` 265 | }{s.c.nextEventID(), "unsubscribe", s.id}) 266 | if err != nil { 267 | return err 268 | } 269 | s.c.mu.Lock() 270 | if ch, ok := s.c.subs[s.id]; ok { 271 | close(ch) 272 | delete(s.c.subs, s.id) 273 | } 274 | s.c.mu.Unlock() 275 | return nil 276 | } 277 | -------------------------------------------------------------------------------- /airlock/airlock_test.go: -------------------------------------------------------------------------------- 1 | package airlock 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | c, err := NewClient("http://localhost:80", "lidlut-tabwed-pillex-ridrup") 13 | if err != nil { 14 | t.Skipf("Spin up a fakezod to test (got error: %v)", err) 15 | } 16 | s, err := c.Subscribe("zod", "chat-store", "/mailbox/~/~zod/mc") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | var events []json.RawMessage 21 | done := make(chan struct{}) 22 | go func() { 23 | for e := range s.Events { 24 | events = append(events, e) 25 | } 26 | close(done) 27 | }() 28 | 29 | now := time.Now().Unix() * 1000 30 | msg := json.RawMessage(fmt.Sprintf(` 31 | { 32 | "message": { 33 | "path": "/~/~zod/mc", 34 | "envelope": { 35 | "uid": "0v20l.5k520.74net.u5qnm.vlbn4.9on3i.80m7n.6c2fq.s7lsj.l5lcr.8d7q7.klh1f.c6a33.8o75q.pqsh2.kmuqn.5694m.9dg1q.ulkv8.gk8ak.sjobd", 36 | "number": 1, 37 | "author": "~zod", 38 | "when": %v, 39 | "letter": {"text": "hello there!"} 40 | } 41 | } 42 | }`, now)) 43 | if err := c.Poke("zod", "chat-hook", "json", msg); err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if err := s.Unsubscribe(); err != nil { 48 | t.Fatal(err) 49 | } 50 | if err := c.Delete(); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | <-done 55 | // should have received an event with our message in it 56 | var found bool 57 | for _, e := range events { 58 | found = found || strings.Contains(string(e), "hello there!") 59 | } 60 | if !found { 61 | t.Fatal("message not found in subscription events") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /atom/atom.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/hex" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // An Aura is a type hint that controls how an Atom is printed. 15 | type Aura string 16 | 17 | // Atom auras. 18 | const ( 19 | AuraAtom = "" // no aura 20 | AuraD = "d" // date 21 | AuraDA = "da" // absolute date 22 | AuraDR = "dr" // relative date 23 | AuraP = "p" // phonemic base (ship name) 24 | AuraR = "r" // IEEE floating-point 25 | AuraRD = "rd" // double precision (64 bits) 26 | AuraRH = "rh" // half precision (16 bits) 27 | AuraRQ = "rq" // quad precision (128 bits) 28 | AuraRS = "rs" // single precision (32 bits) 29 | AuraS = "s" // signed integer, sign bit low 30 | AuraSB = "sb" // signed binary 31 | AuraSD = "sd" // signed decimal 32 | AuraSV = "sv" // signed base32 33 | AuraSW = "sw" // signed base64 34 | AuraSX = "sx" // signed hexadecimal 35 | AuraT = "t" // UTF-8 text (cord) 36 | AuraTA = "ta" // ASCII text (knot) 37 | AuraTAS = "tas" // ASCII text symbol (term) 38 | AuraU = "u" // unsigned integer 39 | AuraUB = "ub" // unsigned binary 40 | AuraUD = "ud" // unsigned decimal 41 | AuraUV = "uv" // unsigned base32 42 | AuraUW = "uw" // unsigned base64 43 | AuraUX = "ux" // unsigned hexadecimal 44 | ) 45 | 46 | // NestsIn returns whether b nests in a. 47 | func (a Aura) NestsIn(b Aura) bool { 48 | return len(a) >= len(b) && a[:len(b)] == b 49 | } 50 | 51 | // An Atom is a natural number. 52 | type Atom struct { 53 | i *big.Int 54 | aura Aura 55 | } 56 | 57 | var uwEnc = base64.NewEncoding("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-~") 58 | 59 | // String implements fmt.Stringer, rendering the Atom according to its aura. 60 | func (a Atom) String() string { 61 | if a.aura.NestsIn("s") { 62 | n := new(big.Int).Add(a.i, big.NewInt(1)) 63 | u := Atom{n.Rsh(n, 1), "u" + a.aura[1:]} 64 | return "--"[a.i.Bit(0):] + u.String() 65 | } 66 | 67 | switch a.aura { 68 | default: 69 | panic("unsupported aura") 70 | case AuraP: 71 | return formatP(a.i) 72 | case AuraDA: 73 | return "~" + formatDate(a.i) 74 | case AuraDR: 75 | return "~" + formatDuration(a.i) 76 | case AuraD, AuraR: 77 | return "0x" + a.i.Text(16) 78 | case AuraRD: 79 | return ".~" + formatFloat(math.Float64frombits(a.i.Uint64()), 64) 80 | case AuraRH: 81 | return ".~~" + float16(uint16(a.i.Uint64())) 82 | case AuraRS: 83 | return "." + formatFloat(float64(math.Float32frombits(uint32(a.i.Uint64()))), 32) 84 | case AuraRQ: 85 | return ".~~~" + float128(a.i) 86 | case AuraT: 87 | return "'" + string(flip(a.i.Bytes())) + "'" 88 | case AuraTA: 89 | return "~." + string(flip(a.i.Bytes())) 90 | case AuraTAS: 91 | if a.i.BitLen() == 0 { 92 | return "%$" 93 | } 94 | return "%" + string(flip(a.i.Bytes())) 95 | case AuraAtom, AuraU, AuraUD: 96 | return formatInt(a.i.Text(10), 3) 97 | case AuraUB: 98 | return "0b" + formatInt(a.i.Text(2), 4) 99 | case AuraUV: 100 | return "0v" + formatInt(a.i.Text(32), 5) 101 | case AuraUW: 102 | if a.i.BitLen() == 0 { 103 | return "0w0" 104 | } 105 | return "0w" + formatInt(uwEnc.EncodeToString(pad(a.i.Bytes(), 3)), 5) 106 | case AuraUX: 107 | return "0x" + formatInt(a.i.Text(16), 4) 108 | } 109 | } 110 | 111 | // Cast returns a copy of a with the specified aura. 112 | func (a Atom) Cast(aura Aura) Atom { 113 | return Atom{ 114 | i: new(big.Int).Set(a.i), 115 | aura: aura, 116 | } 117 | } 118 | 119 | // Format is shorthand for a.Cast(aura).String(). 120 | func (a Atom) Format(aura Aura) string { 121 | return a.Cast(aura).String() 122 | } 123 | 124 | // New initializes an Atom with a *big.Int. 125 | func New(i *big.Int) Atom { 126 | return Atom{ 127 | i: new(big.Int).Set(i), 128 | aura: AuraAtom, 129 | } 130 | } 131 | 132 | // New64 initializes an Atom with a uint64. 133 | func New64(u uint64) Atom { 134 | return New(new(big.Int).SetUint64(u)) 135 | } 136 | 137 | // FromBytes parses an Atom from a []byte. 138 | func FromBytes(b []byte) Atom { 139 | return New(new(big.Int).SetBytes(b)) 140 | } 141 | 142 | func pad(b []byte, n int) []byte { 143 | for len(b)%n != 0 { 144 | b = append([]byte{0}, b...) 145 | } 146 | return b 147 | } 148 | 149 | func flip(b []byte) []byte { 150 | for i := range b[:len(b)/2] { 151 | j := len(b) - i - 1 152 | b[i], b[j] = b[j], b[i] 153 | } 154 | return b 155 | } 156 | 157 | func formatP(a *big.Int) string { 158 | var b []byte 159 | b = a.Bytes() 160 | if len(b) == 0 { 161 | b = []byte{0} 162 | } 163 | if len(b) > 1 { 164 | b = pad(b, 2) 165 | } 166 | var buf strings.Builder 167 | buf.Grow(len(b)*3 + len(b)/2 + len(b)/8) 168 | buf.WriteByte('~') 169 | for i, c := range b { 170 | j := len(b) - i 171 | if i > 0 && j%2 == 0 { 172 | buf.WriteByte('-') 173 | if j%8 == 0 { 174 | buf.WriteByte('-') 175 | } 176 | } 177 | if j%2 == 0 { 178 | buf.WriteString(prefixes[c]) 179 | } else { 180 | buf.WriteString(suffixes[c]) 181 | } 182 | } 183 | return buf.String() 184 | } 185 | 186 | func formatFloat(f float64, bits int) string { 187 | s := strings.ToLower(strconv.FormatFloat(f, 'e', -1, bits)) 188 | if !strings.Contains(s, "e") { 189 | return strings.TrimPrefix(s, "+") // inf or nan 190 | } 191 | t := strings.Split(s, "e") 192 | frac := t[0] 193 | sign := strings.TrimPrefix(t[1][:1], "+") 194 | exp := strings.TrimLeft(t[1][1:], "0") 195 | return strings.TrimSuffix(frac+"e"+sign+exp, "e") 196 | } 197 | 198 | func float16(bits uint16) string { 199 | sign := "" 200 | if bits&0x8000 != 0 { 201 | sign = "-" 202 | } 203 | exp := int(bits & 0x7C00 >> 10) 204 | frac := bits & 0x03FF 205 | x := new(big.Float).SetPrec(11) 206 | lead := 1 207 | exponent := exp - 15 208 | switch exp { 209 | case 0x1F: 210 | if frac == 0 { 211 | return sign + "inf" 212 | } 213 | return "nan" 214 | case 0x00: 215 | if frac == 0 { 216 | return sign + "0" 217 | } 218 | lead = 0 219 | exponent = -14 220 | } 221 | x.Parse(fmt.Sprintf("%s0b%d.%010bp%d", sign, lead, frac, exponent), 0) 222 | t := strings.Split(x.Text('e', -1), "e") 223 | sign = strings.TrimLeft(t[1][:1], "+") 224 | t[1] = strings.TrimLeft(t[1][1:], "0") 225 | if t[1] == "" { 226 | return t[0] 227 | } 228 | return t[0] + "e" + sign + t[1] 229 | } 230 | 231 | func float128(i *big.Int) string { 232 | b := i.Uint64() 233 | a := new(big.Int).Rsh(i, 64).Uint64() 234 | 235 | sign := "" 236 | if a&0x8000000000000000 != 0 { 237 | sign = "-" 238 | } 239 | exp := int(a&0x7FFF000000000000) >> 48 240 | frac1, frac2 := a&0x0000FFFFFFFFFFFF, b 241 | x := new(big.Float).SetPrec(113) 242 | lead := 1 243 | exponent := exp - 16383 244 | switch exp { 245 | case 0x7FFF: 246 | if frac1 == 0 && frac2 == 0 { 247 | return sign + "inf" 248 | } 249 | return "nan" 250 | case 0x00: 251 | if frac1 == 0 && frac2 == 0 { 252 | return sign + "0" 253 | } 254 | lead = 0 255 | exponent = -16382 256 | } 257 | x.Parse(fmt.Sprintf("%s0b%d.%sp%d", sign, lead, fmt.Sprintf("%048b%064b", frac1, frac2), exponent), 0) 258 | t := strings.Split(x.Text('e', -1), "e") 259 | sign = strings.TrimLeft(t[1][:1], "+") 260 | t[1] = strings.TrimLeft(t[1][1:], "0") 261 | if t[1] == "" { 262 | return t[0] 263 | } 264 | return t[0] + "e" + sign + t[1] 265 | } 266 | 267 | var jesus = time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC).Unix() 268 | var oneSec = new(big.Int).Lsh(big.NewInt(1), 64) 269 | 270 | func formatDate(a *big.Int) string { 271 | secs, rem := new(big.Int).QuoRem(a, oneSec, new(big.Int)) 272 | date := time.Unix(jesus+int64(secs.Uint64())-9223372029693628800, 0).UTC().Format("2006.1.2..15.04.05") 273 | frac := formatInt("."+hex.EncodeToString(rem.Bytes()), 4) 274 | return strings.TrimRight(strings.TrimLeft(date, "0"), ".0") + strings.TrimRight(frac, ".0") 275 | } 276 | 277 | func formatDuration(a *big.Int) string { 278 | if a.BitLen() == 0 { 279 | return "s0" 280 | } 281 | secs, rem := new(big.Int).QuoRem(a, oneSec, new(big.Int)) 282 | mins, secRem := new(big.Int).QuoRem(secs, big.NewInt(60), new(big.Int)) 283 | hrs, minRem := new(big.Int).QuoRem(mins, big.NewInt(60), new(big.Int)) 284 | days, hrRem := new(big.Int).QuoRem(hrs, big.NewInt(24), new(big.Int)) 285 | var sb strings.Builder 286 | if days.BitLen() > 0 { 287 | sb.WriteString(".d" + days.String()) 288 | } 289 | if hrRem.BitLen() > 0 { 290 | sb.WriteString(".h" + hrRem.String()) 291 | } 292 | if minRem.BitLen() > 0 { 293 | sb.WriteString(".m" + minRem.String()) 294 | } 295 | if secRem.BitLen() > 0 { 296 | sb.WriteString(".s" + secRem.String()) 297 | } 298 | if rem.BitLen() > 0 { 299 | if sb.Len() == 0 { 300 | sb.WriteString("s0") 301 | } 302 | buf := []byte(".0000000000000000") 303 | rt := rem.Text(16) 304 | copy(buf[len(buf)-len(rt):], rt) 305 | sb.WriteString(formatInt(string(buf), 4)) 306 | } 307 | return strings.TrimPrefix(sb.String(), ".") 308 | } 309 | 310 | func formatInt(s string, n int) string { 311 | if s == "0" { 312 | return "0" 313 | } 314 | s = strings.TrimLeft(s, "0") 315 | if len(s) < n { 316 | return s 317 | } 318 | i := len(s) % n 319 | if i == 0 { 320 | i = n 321 | } 322 | var buf strings.Builder 323 | buf.Grow(len(s) + len(s)/n) 324 | buf.WriteString(s[:i]) 325 | for ; i < len(s); i += n { 326 | buf.WriteByte('.') 327 | buf.WriteString(s[i:][:n]) 328 | } 329 | return buf.String() 330 | } 331 | 332 | var phonemeIndex = func() map[string]uint8 { 333 | m := make(map[string]uint8) 334 | for i, p := range prefixes { 335 | m[p] = uint8(i) 336 | } 337 | for i, p := range suffixes { 338 | m[p] = uint8(i) 339 | } 340 | return m 341 | }() 342 | 343 | var prefixes = [256]string{ 344 | "doz", "mar", "bin", "wan", "sam", "lit", "sig", "hid", "fid", "lis", "sog", "dir", "wac", "sab", "wis", "sib", 345 | "rig", "sol", "dop", "mod", "fog", "lid", "hop", "dar", "dor", "lor", "hod", "fol", "rin", "tog", "sil", "mir", 346 | "hol", "pas", "lac", "rov", "liv", "dal", "sat", "lib", "tab", "han", "tic", "pid", "tor", "bol", "fos", "dot", 347 | "los", "dil", "for", "pil", "ram", "tir", "win", "tad", "bic", "dif", "roc", "wid", "bis", "das", "mid", "lop", 348 | "ril", "nar", "dap", "mol", "san", "loc", "nov", "sit", "nid", "tip", "sic", "rop", "wit", "nat", "pan", "min", 349 | "rit", "pod", "mot", "tam", "tol", "sav", "pos", "nap", "nop", "som", "fin", "fon", "ban", "mor", "wor", "sip", 350 | "ron", "nor", "bot", "wic", "soc", "wat", "dol", "mag", "pic", "dav", "bid", "bal", "tim", "tas", "mal", "lig", 351 | "siv", "tag", "pad", "sal", "div", "dac", "tan", "sid", "fab", "tar", "mon", "ran", "nis", "wol", "mis", "pal", 352 | "las", "dis", "map", "rab", "tob", "rol", "lat", "lon", "nod", "nav", "fig", "nom", "nib", "pag", "sop", "ral", 353 | "bil", "had", "doc", "rid", "moc", "pac", "rav", "rip", "fal", "tod", "til", "tin", "hap", "mic", "fan", "pat", 354 | "tac", "lab", "mog", "sim", "son", "pin", "lom", "ric", "tap", "fir", "has", "bos", "bat", "poc", "hac", "tid", 355 | "hav", "sap", "lin", "dib", "hos", "dab", "bit", "bar", "rac", "par", "lod", "dos", "bor", "toc", "hil", "mac", 356 | "tom", "dig", "fil", "fas", "mit", "hob", "har", "mig", "hin", "rad", "mas", "hal", "rag", "lag", "fad", "top", 357 | "mop", "hab", "nil", "nos", "mil", "fop", "fam", "dat", "nol", "din", "hat", "nac", "ris", "fot", "rib", "hoc", 358 | "nim", "lar", "fit", "wal", "rap", "sar", "nal", "mos", "lan", "don", "dan", "lad", "dov", "riv", "bac", "pol", 359 | "lap", "tal", "pit", "nam", "bon", "ros", "ton", "fod", "pon", "sov", "noc", "sor", "lav", "mat", "mip", "fip", 360 | } 361 | 362 | var suffixes = [256]string{ 363 | "zod", "nec", "bud", "wes", "sev", "per", "sut", "let", "ful", "pen", "syt", "dur", "wep", "ser", "wyl", "sun", 364 | "ryp", "syx", "dyr", "nup", "heb", "peg", "lup", "dep", "dys", "put", "lug", "hec", "ryt", "tyv", "syd", "nex", 365 | "lun", "mep", "lut", "sep", "pes", "del", "sul", "ped", "tem", "led", "tul", "met", "wen", "byn", "hex", "feb", 366 | "pyl", "dul", "het", "mev", "rut", "tyl", "wyd", "tep", "bes", "dex", "sef", "wyc", "bur", "der", "nep", "pur", 367 | "rys", "reb", "den", "nut", "sub", "pet", "rul", "syn", "reg", "tyd", "sup", "sem", "wyn", "rec", "meg", "net", 368 | "sec", "mul", "nym", "tev", "web", "sum", "mut", "nyx", "rex", "teb", "fus", "hep", "ben", "mus", "wyx", "sym", 369 | "sel", "ruc", "dec", "wex", "syr", "wet", "dyl", "myn", "mes", "det", "bet", "bel", "tux", "tug", "myr", "pel", 370 | "syp", "ter", "meb", "set", "dut", "deg", "tex", "sur", "fel", "tud", "nux", "rux", "ren", "wyt", "nub", "med", 371 | "lyt", "dus", "neb", "rum", "tyn", "seg", "lyx", "pun", "res", "red", "fun", "rev", "ref", "mec", "ted", "rus", 372 | "bex", "leb", "dux", "ryn", "num", "pyx", "ryg", "ryx", "fep", "tyr", "tus", "tyc", "leg", "nem", "fer", "mer", 373 | "ten", "lus", "nus", "syl", "tec", "mex", "pub", "rym", "tuc", "fyl", "lep", "deb", "ber", "mug", "hut", "tun", 374 | "byl", "sud", "pem", "dev", "lur", "def", "bus", "bep", "run", "mel", "pex", "dyt", "byt", "typ", "lev", "myl", 375 | "wed", "duc", "fur", "fex", "nul", "luc", "len", "ner", "lex", "rup", "ned", "lec", "ryd", "lyd", "fen", "wel", 376 | "nyd", "hus", "rel", "rud", "nes", "hes", "fet", "des", "ret", "dun", "ler", "nyr", "seb", "hul", "ryl", "lud", 377 | "rem", "lys", "fyn", "wer", "ryc", "sug", "nys", "nyl", "lyn", "dyn", "dem", "lux", "fed", "sed", "bec", "mun", 378 | "lyr", "tes", "mud", "nyt", "byr", "sen", "weg", "fyr", "mur", "tel", "rep", "teg", "pec", "nel", "nev", "fes", 379 | } 380 | -------------------------------------------------------------------------------- /atom/atom_test.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | ) 7 | 8 | func TestFormat(t *testing.T) { 9 | testCases := []struct { 10 | dec string 11 | exp map[Aura]string 12 | }{ 13 | { 14 | dec: "0", 15 | exp: map[Aura]string{ 16 | "dr": "~s0", 17 | "p": "~zod", 18 | "s": "--0", 19 | "sb": "--0b0", 20 | "sv": "--0v0", 21 | "sw": "--0w0", 22 | "sx": "--0x0", 23 | "t": "''", 24 | "ta": "~.", 25 | "tas": "%$", 26 | "u": "0", 27 | "ub": "0b0", 28 | "uv": "0v0", 29 | "uw": "0w0", 30 | "ux": "0x0", 31 | }, 32 | }, 33 | { 34 | dec: "56", 35 | exp: map[Aura]string{ 36 | "dr": "~s0..0000.0000.0000.0038", 37 | "p": "~bes", 38 | "s": "--28", 39 | "sb": "--0b1.1100", 40 | "sv": "--0vs", 41 | "sw": "--0ws", 42 | "sx": "--0x1c", 43 | "t": "'8'", 44 | "ta": "~.8", 45 | "tas": "%8", 46 | "u": "56", 47 | "ub": "0b11.1000", 48 | "uv": "0v1o", 49 | "uw": "0wU", 50 | "ux": "0x38", 51 | }, 52 | }, 53 | { 54 | dec: "62565", 55 | exp: map[Aura]string{ 56 | "dr": "~s0..0000.0000.0000.f465", 57 | "p": "~bonwet", 58 | "s": "-31.283", 59 | "sb": "-0b111.1010.0011.0011", 60 | "sv": "-0vuhj", 61 | "sw": "-0w7EP", 62 | "sx": "-0x7a33", 63 | "u": "62.565", 64 | "ub": "0b1111.0100.0110.0101", 65 | "uv": "0v1t35", 66 | "uw": "0wfhB", 67 | "ux": "0xf465", 68 | }, 69 | }, 70 | { 71 | dec: "1000056", 72 | exp: map[Aura]string{ 73 | "dr": "~s0..0000.0000.000f.4278", 74 | "p": "~dozsun-dapfel", 75 | "s": "--500.028", 76 | "sb": "--0b111.1010.0001.0011.1100", 77 | "sv": "--0vf89s", 78 | "sw": "--0w1W4Y", 79 | "sx": "--0x7.a13c", 80 | "u": "1.000.056", 81 | "ub": "0b1111.0100.0010.0111.1000", 82 | "uv": "0vugjo", 83 | "uw": "0w3Q9U", 84 | "ux": "0xf.4278", 85 | }, 86 | }, 87 | { 88 | dec: "100000056", 89 | exp: map[Aura]string{ 90 | "dr": "~s0..0000.0000.05f5.e138", 91 | "p": "~litsen-larbes", 92 | "r": "0x5f5e138", 93 | "rd": ".~4.94065923e-316", 94 | "rh": ".~~-6.68e2", 95 | // "rq": ".~~~6.47517875e-4958", 96 | "rs": ".2.3122422e-35", 97 | "s": "--50.000.028", 98 | "sb": "--0b10.1111.1010.1111.0000.1001.1100", 99 | "sv": "--0v1.fls4s", 100 | "sw": "--0w2-L2s", 101 | "sx": "--0x2fa.f09c", 102 | "u": "100.000.056", 103 | "ub": "0b101.1111.0101.1110.0001.0011.1000", 104 | "uv": "0v2.vbo9o", 105 | "uw": "0w5Zu4U", 106 | "ux": "0x5f5.e138", 107 | }, 108 | }, 109 | { 110 | dec: "50000000495056", 111 | exp: map[Aura]string{ 112 | "dr": "~s0..0000.2d79.8844.add0", 113 | "p": "~boltud-nodsub-pocnyd", 114 | "s": "--25.000.000.247.528", 115 | "sb": "--0b1.0110.1011.1100.1100.0100.0010.0010.0101.0110.1110.1000", 116 | "sv": "--0vmnj2.24ln8", 117 | "sw": "--0w5HP.48BrE", 118 | "sx": "--0x16bc.c422.56e8", 119 | "u": "50.000.000.495.056", 120 | "ub": "0b10.1101.0111.1001.1000.1000.0100.0100.1010.1101.1101.0000", 121 | "uv": "0v1df64.49beg", 122 | "uw": "0wbnC.8haTg", 123 | "ux": "0x2d79.8844.add0", 124 | }, 125 | }, 126 | { 127 | dec: "324856418037915076923468482958044471810", 128 | exp: map[Aura]string{ 129 | "dr": "~d203825251263059.h10.m53.s26..4205.e38f.c19c.a202", 130 | "p": "~bonwet-dopzod-marnec-litpub--dapper-walrus-digleg-mogbud", 131 | "s": "--162.428.209.018.957.538.461.734.241.479.022.235.905", 132 | "sv": "--0v3.q6a4g.0040g.b9i20.nhovg.csk81", 133 | "sw": "--0w1W.cEA00.822QO.42Ysv.wPB41", 134 | "sx": "--0x7a32.8900.0080.82d3.2102.f1c7.e0ce.5101", 135 | "u": "324.856.418.037.915.076.923.468.482.958.044.471.810", 136 | "uv": "0v7.kck90.00810.mj441.f3hv0.pp8g2", 137 | "uw": "0w3Q.ph800.g45FA.85UU~.1Da82", 138 | "ux": "0xf465.1200.0101.05a6.4205.e38f.c19c.a202", 139 | }, 140 | }, 141 | { 142 | dec: "3180018672171963293882650178620901216809935565260671847783682737515400389475150158146305", 143 | exp: map[Aura]string{ 144 | "dr": "~d1995244880158166508274203861324441490350683664059201588050179639.h13.m19.s28..d554.9838.9f24.ab01", 145 | "p": "~dozsut-socbec-milbyn--difseg-dinnyx-tirnes-sipmer--matrem-docfes-sardul-mirhep--pansyd-dannul-wolsef-dasrem--fopweb-falbes-patpes-bosnec", 146 | "s": "-1.590.009.336.085.981.646.941.325.089.310.450.608.404.967.782.630.335.923.891.841.368.757.700.194.737.575.079.073.153", 147 | "sv": "-0vpie.tl1d7.62til.plqhf.pvvf0.ibvua.c8vbd.71tqm.4fkt3.ro6la.ic3h7.p4lc1", 148 | "sw": "-0w3cDt.G5FP2.XaKqW.y~f~L.19v~a.ozWSD.3Tlyf.FQuY6.GGj1N.fABm1", 149 | "sx": "-0x3.3277.6a16.9cc2.ecab.9aea.2fcf.fef0.497f.f298.8fad.a70f.7562.3e9d.1ef0.6aaa.4c1c.4f92.5581", 150 | "u": "3.180.018.672.171.963.293.882.650.178.620.901.216.809.935.565.260.671.847.783.682.737.515.400.389.475.150.158.146.305", 151 | "uv": "0v1j4t.ra2qe.c5r5b.jbl2v.jvuu1.4nvsk.ohumq.e3rlc.8v9q7.ngdal.4o72f.i9ao1", 152 | "uw": "0w6peX.kbjC5.SlsRR.5-v~u.2i~-k.N7RJe.7KH4v.jEZUd.lkC3y.v9aI1", 153 | "ux": "0x6.64ee.d42d.3985.d957.35d4.5f9f.fde0.92ff.e531.1f5b.4e1e.eac4.7d3a.3de0.d554.9838.9f24.ab01", 154 | }, 155 | }, 156 | { 157 | dec: "125762588864358", 158 | exp: map[Aura]string{ 159 | "t": "'foobar'", 160 | "ta": "~.foobar", 161 | "tas": "%foobar", 162 | }, 163 | }, 164 | { 165 | dec: "8684515", 166 | exp: map[Aura]string{ 167 | "t": "'ツ'", 168 | "ta": "~.ツ", 169 | "tas": "%ツ", 170 | }, 171 | }, 172 | } 173 | for _, test := range testCases { 174 | i, _ := new(big.Int).SetString(test.dec, 10) 175 | for aura, exp := range test.exp { 176 | got := Atom{i: i}.Format(aura) 177 | if got != exp { 178 | t.Errorf("`@%v`%v\nexp: %v\ngot: %v", aura, test.dec, exp, got) 179 | } 180 | } 181 | } 182 | } 183 | 184 | func TestFloat(t *testing.T) { 185 | testCases := []struct { 186 | aura Aura 187 | exp map[string]string 188 | }{ 189 | { 190 | aura: "rq", 191 | exp: map[string]string{ 192 | "0x0": ".~~~0", 193 | "0x1": ".~~~6.475175119438025110924438958227647e-4966", 194 | "0x3fff0000000000000000000000000000": ".~~~1", 195 | "0x80000000000000000000000000000000": ".~~~-0", 196 | "0x7fff0000000000000000000000000000": ".~~~inf", 197 | "0xffff0000000000000000000000000000": ".~~~-inf", 198 | "0x7fff8000000000000000000000000000": ".~~~nan", 199 | }, 200 | }, 201 | { 202 | aura: "rs", 203 | exp: map[string]string{ 204 | "0x0": ".0", 205 | "0x1": ".1e-45", 206 | "0x3f800000": ".1", 207 | "0x80000000": ".-0", 208 | "0x7f800000": ".inf", 209 | "0xff800000": ".-inf", 210 | "0x7fc00000": ".nan", 211 | }, 212 | }, 213 | { 214 | aura: "rh", 215 | exp: map[string]string{ 216 | "0x0": ".~~0", 217 | "0x1": ".~~5.96e-8", 218 | "0x3c00": ".~~1", 219 | "0x8000": ".~~-0", 220 | "0x7c00": ".~~inf", 221 | "0xfc00": ".~~-inf", 222 | "0x7e00": ".~~nan", 223 | }, 224 | }, 225 | } 226 | for _, test := range testCases { 227 | for hex, exp := range test.exp { 228 | i, _ := new(big.Int).SetString(hex, 0) 229 | got := Atom{i: i}.Format(test.aura) 230 | if got != exp { 231 | t.Errorf("`@%v`%v\nexp: %v\ngot: %v", test.aura, hex, exp, got) 232 | } 233 | } 234 | } 235 | } 236 | 237 | func TestDateAbsolute(t *testing.T) { 238 | testCases := []struct { 239 | hex, exp string 240 | }{ 241 | { 242 | hex: "0x8000000d2da1efc901fa000000000506", 243 | exp: "~2020.7.7..02.47.37..01fa.0000.0000.0506", 244 | }, 245 | { 246 | hex: "0x8000000d2da1efc901fa000000000000", 247 | exp: "~2020.7.7..02.47.37..01fa", 248 | }, 249 | { 250 | hex: "0x7ffffffe570c16800000000000000000", 251 | exp: "~1.1.1", 252 | }, 253 | } 254 | for _, test := range testCases { 255 | i, _ := new(big.Int).SetString(test.hex, 0) 256 | got := Atom{i: i}.Format("da") 257 | if got != test.exp { 258 | t.Errorf("`@%v`%v\nexp: %v\ngot: %v", "da", test.hex, test.exp, got) 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module lukechampine.com/urbit 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/llir/llvm v0.3.1 7 | github.com/spaolacci/murmur3 v1.1.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 3 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 4 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 7 | github.com/llir/ll v0.0.0-20200425014433-60cd8feecf92/go.mod h1:8W5HJz80PitAyPZUpOcljQxTu6LD5YKW1URTo+OjVoc= 8 | github.com/llir/llvm v0.3.1 h1:kd38xQGb8HcnoLPQp9IILYMAriQWNVNhrLVOPMXFK6I= 9 | github.com/llir/llvm v0.3.1/go.mod h1:XJV3Vp2o0lNO4hEuJ9Kvm0ix1MsMT4pUgynR0ii2yyA= 10 | github.com/mewmew/float v0.0.0-20191226120903-16bbe2fdd85e h1:KCD7E/8LKwDsC5ymlEWJ3xCiSPaCywrS/psToBMOBH4= 11 | github.com/mewmew/float v0.0.0-20191226120903-16bbe2fdd85e/go.mod h1:O+xb+8ycBNHzJicFVs7GRWtruD4tVZI0huVnw5TM01E= 12 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 13 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 15 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 16 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 18 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 19 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 20 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 21 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 22 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 28 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 29 | golang.org/x/tools v0.0.0-20200504193531-9bfbc385433f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 30 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 31 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 32 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 33 | -------------------------------------------------------------------------------- /hoon/ast/ast.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "lukechampine.com/urbit/hoon/token" 8 | ) 9 | 10 | type Node interface { 11 | isNode() 12 | } 13 | 14 | func (Buc) isNode() {} 15 | func (Pat) isNode() {} 16 | func (Dot) isNode() {} 17 | func (Face) isNode() {} 18 | func (Slot) isNode() {} 19 | func (Tis) isNode() {} 20 | func (Num) isNode() {} 21 | func (Rune) isNode() {} 22 | func (Cell) isNode() {} 23 | 24 | type Buc struct { 25 | Tok token.Token 26 | } 27 | 28 | type Pat struct { 29 | Tok token.Token 30 | } 31 | 32 | // TODO: replace with wing? 33 | type Dot struct { 34 | Tok token.Token 35 | } 36 | 37 | type Face struct { 38 | Tok token.Token 39 | Name string 40 | } 41 | 42 | type Slot struct { 43 | Tok token.Token 44 | Address string 45 | } 46 | 47 | type Tis struct { 48 | Tok token.Token 49 | Left Node 50 | Right Node 51 | } 52 | 53 | type Num struct { 54 | Tok token.Token 55 | Int string 56 | } 57 | 58 | type Rune struct { 59 | Tok token.Token 60 | Lit string 61 | Args []Node 62 | } 63 | 64 | type Cell struct { 65 | Tok token.Token 66 | Head Node 67 | Tail Node 68 | } 69 | 70 | func (r Rune) Reduce() Rune { 71 | switch r.Lit { 72 | // these are the only "real" runes; every other rune reduces to one of these 73 | case "=~", "%=", "?:": 74 | return r 75 | 76 | // these runes are just flipped versions of other runes 77 | case "=<", "%.": 78 | return Rune{ 79 | Lit: map[string]string{ 80 | "=<": "=>", 81 | "=-": "=+", 82 | "%.": "%-", 83 | "?.": "?:", 84 | }[r.Lit], 85 | Args: []Node{ 86 | r.Args[1], 87 | r.Args[0], 88 | }, 89 | }.Reduce() 90 | 91 | // tis 92 | case "=>": 93 | return Rune{ 94 | Lit: "=~", 95 | Args: r.Args, 96 | }.Reduce() 97 | case "=+": 98 | return Rune{ 99 | Lit: "=>", 100 | Args: []Node{ 101 | Cell{ 102 | Head: r.Args[0], 103 | Tail: Dot{}, 104 | }, 105 | r.Args[1], 106 | }, 107 | }.Reduce() 108 | case "=.", "=:": 109 | return Rune{ 110 | Lit: "=>", 111 | Args: []Node{ 112 | Rune{ 113 | Lit: "%_", 114 | Args: append([]Node{ 115 | Dot{}, 116 | }, r.Args[:len(r.Args)-1]...), 117 | }, 118 | r.Args[len(r.Args)-1], 119 | }, 120 | }.Reduce() 121 | case "=|": 122 | return Rune{ 123 | Lit: "=+", 124 | Args: []Node{ 125 | Rune{ 126 | Lit: "^*", 127 | Args: []Node{r.Args[0]}, 128 | }, 129 | r.Args[1], 130 | }, 131 | }.Reduce() 132 | case "=/": 133 | return Rune{ 134 | Lit: "=+", 135 | Args: []Node{ 136 | Tis{ 137 | Left: r.Args[0], 138 | Right: r.Args[1], 139 | }, 140 | r.Args[2], 141 | }, 142 | }.Reduce() 143 | case "=;": 144 | return Rune{ 145 | Lit: "=/", 146 | Args: []Node{ 147 | r.Args[0], 148 | r.Args[2], 149 | r.Args[1], 150 | }, 151 | }.Reduce() 152 | case "=?": 153 | return Rune{ 154 | Lit: "=.", 155 | Args: []Node{ 156 | r.Args[0], 157 | Rune{ 158 | Lit: "?:", 159 | Args: []Node{ 160 | r.Args[1], 161 | r.Args[2], 162 | r.Args[0], 163 | }, 164 | }, 165 | r.Args[3], 166 | }, 167 | }.Reduce() 168 | 169 | // bar 170 | case "|.": 171 | return Rune{ 172 | Lit: "|%", 173 | Args: []Node{ 174 | Buc{}, 175 | r.Args[0], 176 | }, 177 | }.Reduce() 178 | case "|_": 179 | return Rune{ 180 | Lit: "=|", 181 | Args: []Node{ 182 | r.Args[0], 183 | Rune{ 184 | Lit: "|%", 185 | Args: r.Args[1:], 186 | }, 187 | }, 188 | }.Reduce() 189 | case "|=": 190 | return Rune{ 191 | Lit: "=|", 192 | Args: []Node{ 193 | r.Args[0], 194 | Rune{ 195 | Lit: "|.", 196 | Args: []Node{r.Args[1]}, 197 | }, 198 | }, 199 | }.Reduce() 200 | case "|^", "|-": 201 | return Rune{ 202 | Lit: "=>", 203 | Args: []Node{ 204 | Rune{ 205 | Lit: "|%", 206 | Args: append([]Node{ 207 | Buc{}, 208 | r.Args[0], 209 | }, r.Args[1:]...), 210 | }, 211 | Buc{}, 212 | }, 213 | }.Reduce() 214 | 215 | // cen 216 | case "%_": 217 | return Rune{ 218 | Lit: "^+", 219 | Args: []Node{ 220 | r.Args[0], 221 | Rune{ 222 | Lit: "%=", 223 | Args: r.Args[1:], 224 | }, 225 | }, 226 | }.Reduce() 227 | case "%~": 228 | return Rune{ 229 | Lit: "=+", 230 | Args: []Node{ 231 | r.Args[1], 232 | Rune{ 233 | Lit: "=>", 234 | Args: []Node{ 235 | Rune{ 236 | Lit: "%=", 237 | Args: []Node{ 238 | Slot{Address: "2"}, 239 | Slot{Address: "6"}, 240 | r.Args[2], 241 | }, 242 | }, 243 | r.Args[0], 244 | }, 245 | }, 246 | }, 247 | }.Reduce() 248 | case "%-": 249 | return Rune{ 250 | Lit: "%~", 251 | Args: []Node{Buc{}, r.Args[0], r.Args[1]}, 252 | }.Reduce() 253 | 254 | default: 255 | panic("unhandled reduction " + r.Lit) 256 | } 257 | } 258 | 259 | func Print(w io.Writer, n Node) (err error) { 260 | writeString := func(str string) { 261 | if err == nil { 262 | _, err = io.WriteString(w, str) 263 | } 264 | } 265 | writeNode := func(p Node) { 266 | if err == nil { 267 | err = Print(w, p) 268 | } 269 | } 270 | switch n := n.(type) { 271 | case Buc: 272 | writeString("$") 273 | case Pat: 274 | writeString("@") 275 | case Dot: 276 | writeString(".") 277 | case Face: 278 | writeString(n.Name) 279 | case Tis: 280 | writeNode(n.Left) 281 | writeString("=") 282 | writeNode(n.Right) 283 | case Num: 284 | writeString(n.Int) 285 | case Rune: 286 | switch n.Lit { 287 | case "%=": 288 | writeString(n.Lit) 289 | writeString("(") 290 | writeNode(n.Args[0]) 291 | for i := 1; i < len(n.Args); i += 2 { 292 | if i > 1 { 293 | writeString(",") 294 | } 295 | writeString(" ") 296 | writeNode(n.Args[i]) 297 | writeString(" ") 298 | writeNode(n.Args[i+1]) 299 | } 300 | writeString(")") 301 | case "%-": 302 | writeString("(") 303 | for i, arg := range n.Args { 304 | if i > 0 { 305 | writeString(" ") 306 | } 307 | writeNode(arg) 308 | } 309 | writeString(")") 310 | default: 311 | writeString(n.Lit) 312 | writeString("(") 313 | for i, arg := range n.Args { 314 | if i > 0 { 315 | writeString(" ") 316 | } 317 | writeNode(arg) 318 | } 319 | writeString(")") 320 | } 321 | case Cell: 322 | writeString("[") 323 | writeNode(n.Head) 324 | writeString(" ") 325 | writeNode(n.Tail) 326 | writeString("]") 327 | default: 328 | panic(fmt.Sprintf("unknown node type %T", n)) 329 | } 330 | return 331 | } 332 | -------------------------------------------------------------------------------- /hoon/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "lukechampine.com/urbit/hoon/ast" 7 | "lukechampine.com/urbit/hoon/scanner" 8 | "lukechampine.com/urbit/hoon/token" 9 | ) 10 | 11 | // TODO: determine appropriate values for these 12 | var precedences = map[token.Token]int{ 13 | token.Col: 2, 14 | token.Tis: 2, 15 | } 16 | 17 | type runeEntry struct { 18 | args int 19 | jogging bool 20 | post int // some runes have fixed args *after* the jog 21 | } 22 | 23 | // TODO: probably numerous errors here 24 | var runeTab = map[string]runeEntry{ 25 | ".^": {args: 2}, 26 | ".+": {args: 1}, 27 | ".*": {args: 2}, 28 | ".=": {args: 2}, 29 | ".?": {args: 1}, 30 | "!>": {args: 1}, 31 | "!<": {args: 2}, 32 | "!:": {args: 1}, 33 | "!.": {args: 1}, 34 | "!=": {args: 1}, 35 | "!?": {args: 2}, 36 | "=+": {args: 2}, 37 | "=-": {args: 2}, 38 | "=|": {args: 2}, 39 | "=/": {args: 3}, 40 | "=;": {args: 2}, 41 | "=.": {args: 3}, 42 | "=:": {jogging: true, post: 2}, 43 | "=?": {args: 4}, 44 | "=*": {args: 3}, 45 | "=>": {args: 2}, 46 | "=<": {args: 2}, 47 | "=~": {args: 2}, 48 | "=,": {args: 2}, 49 | "=^": {args: 4}, 50 | "?>": {args: 2}, 51 | "?<": {args: 2}, 52 | "?|": {args: 1, jogging: true}, 53 | "?&": {args: 1, jogging: true}, 54 | "?!": {args: 1}, 55 | "?=": {args: 2}, 56 | "?:": {args: 3}, 57 | "?.": {args: 3}, 58 | "?@": {args: 3}, 59 | "?^": {args: 3}, 60 | "?~": {args: 3}, 61 | "?-": {args: 1, jogging: true}, 62 | "?+": {args: 1, jogging: true}, 63 | "|_": {args: 1, jogging: true}, 64 | "|%": {args: 0, jogging: true}, // cores can be empty 65 | "|:": {args: 2}, 66 | "|.": {args: 1}, 67 | "|-": {args: 1}, 68 | "|?": {args: 1}, 69 | "|^": {args: 2, jogging: true}, 70 | "|~": {args: 2}, 71 | "|=": {args: 2}, 72 | "|*": {args: 2}, 73 | "|@": {args: 2}, 74 | "++": {args: 2}, 75 | "+$": {args: 2}, 76 | "+*": {args: 2}, 77 | "+|": {args: 1}, 78 | ":-": {args: 2}, 79 | ":_": {args: 2}, 80 | ":+": {args: 3}, 81 | ":^": {args: 4}, 82 | ":*": {args: 1, jogging: true}, 83 | ":~": {args: 1, jogging: true}, 84 | "::": {args: 1}, 85 | "%~": {args: 3, jogging: true}, 86 | "%-": {args: 2}, 87 | "%.": {args: 2}, 88 | "%+": {args: 3}, 89 | "%^": {args: 4}, 90 | "%:": {args: 2, jogging: true}, 91 | "%=": {args: 3, jogging: true}, 92 | "%_": {args: 2, jogging: true}, 93 | "%*": {args: 3, jogging: true}, 94 | "^|": {args: 1}, 95 | "^&": {args: 1}, 96 | "^?": {args: 1}, 97 | "^:": {args: 1}, 98 | "^.": {args: 2}, 99 | "^-": {args: 2}, 100 | "^+": {args: 2}, 101 | "^~": {args: 1}, 102 | "^*": {args: 1}, 103 | "^=": {args: 2}, 104 | "$_": {args: 1}, 105 | "$%": {args: 1, jogging: true}, 106 | "$:": {args: 1, jogging: true}, 107 | "$?": {args: 1, jogging: true}, 108 | "$<": {args: 2}, 109 | "$>": {args: 2}, 110 | "$-": {args: 2}, 111 | "$@": {args: 2}, 112 | "$^": {args: 2}, 113 | "$~": {args: 2}, 114 | "$=": {args: 2}, 115 | ";:": {args: 2, jogging: true}, 116 | ";+": {args: 1}, 117 | ";/": {args: 1}, 118 | ";*": {args: 1}, 119 | ";=": {args: 1, jogging: true}, 120 | ";;": {args: 2}, 121 | ";~": {args: 2}, 122 | "~>": {args: 2}, 123 | "~|": {args: 2}, 124 | "~_": {args: 2}, 125 | "~$": {args: 2}, 126 | "~%": {args: 3, jogging: true}, 127 | "~<": {args: 2}, 128 | "~+": {args: 1}, 129 | "~/": {args: 2}, 130 | "~&": {args: 2}, 131 | "~?": {args: 3}, 132 | "~!": {args: 2}, 133 | } 134 | 135 | func hasArms(r string) bool { 136 | switch r { 137 | case "|%", "|^": // TODO 138 | return true 139 | default: 140 | return false 141 | } 142 | } 143 | 144 | type Parser struct { 145 | s *scanner.Scanner 146 | tok token.Token 147 | lit string 148 | } 149 | 150 | func (p *Parser) next() { 151 | p.tok, p.lit = p.s.Scan() 152 | } 153 | 154 | func (p *Parser) consumeWhitespace() { 155 | for p.tok == token.Ace || p.tok == token.Gap { 156 | p.next() 157 | } 158 | } 159 | 160 | func (p *Parser) expect(t token.Token) { 161 | if p.tok != t { 162 | panic(fmt.Sprintf("parse: expected %q, got %q", t, p.tok)) 163 | } 164 | p.next() 165 | } 166 | 167 | func (p *Parser) consumeWide() []ast.Node { 168 | var nodes []ast.Node 169 | for { 170 | nodes = append(nodes, p.parseExpr()) 171 | if p.tok == token.Par { 172 | p.next() 173 | return nodes 174 | } 175 | p.expect(token.Ace) 176 | } 177 | } 178 | 179 | func (p *Parser) consumeWideComma() []ast.Node { 180 | var nodes []ast.Node 181 | for { 182 | nodes = append(nodes, p.parseExpr()) 183 | p.expect(token.Ace) 184 | nodes = append(nodes, p.parseExpr()) 185 | if p.tok == token.Par { 186 | p.next() 187 | return nodes 188 | } 189 | p.expect(token.Com) 190 | p.expect(token.Ace) 191 | } 192 | } 193 | 194 | func (p *Parser) consumeTall(stop token.Token) []ast.Node { 195 | var nodes []ast.Node 196 | for { 197 | p.expect(token.Gap) 198 | if p.tok == stop { 199 | p.next() 200 | return nodes 201 | } 202 | nodes = append(nodes, p.parseExpr()) 203 | } 204 | } 205 | 206 | func (p *Parser) Parse() ast.Node { 207 | p.consumeWhitespace() 208 | n := p.parseExpr() 209 | p.consumeWhitespace() 210 | return n 211 | } 212 | 213 | func (p *Parser) parseExpr() ast.Node { 214 | return p.parseBinaryExpr(1) // lowest precedence 215 | } 216 | 217 | func (p *Parser) parseBinaryExpr(prec int) ast.Node { 218 | n := p.parseUnaryExpr() 219 | for { 220 | op := p.tok 221 | qPrec, ok := precedences[op] 222 | if !ok || qPrec < prec { 223 | break 224 | } 225 | p.next() 226 | next := p.parseBinaryExpr(qPrec + 1) 227 | switch op { 228 | case token.Col: 229 | n = ast.Rune{ 230 | Tok: op, 231 | Lit: "=<", 232 | Args: []ast.Node{n, next}, 233 | } 234 | case token.Tis: 235 | n = ast.Tis{ 236 | Tok: op, 237 | Left: n, 238 | Right: next, 239 | } 240 | default: 241 | panic("unhandled binop") 242 | } 243 | } 244 | return n 245 | } 246 | 247 | func (p *Parser) parseUnaryExpr() ast.Node { 248 | t, lit := p.tok, p.lit 249 | p.next() 250 | switch t { 251 | case token.Face: 252 | if p.tok == token.Pal { 253 | p.next() 254 | return ast.Rune{ 255 | Tok: t, 256 | Lit: "%=", 257 | Args: append([]ast.Node{ast.Face{ 258 | Tok: t, 259 | Name: lit, 260 | }}, p.consumeWideComma()...), 261 | } 262 | } 263 | return ast.Face{Tok: t, Name: lit} 264 | case token.Num: 265 | return ast.Num{Tok: t, Int: lit} 266 | case token.Pat: 267 | return ast.Pat{Tok: t} 268 | case token.Dot: 269 | return ast.Dot{Tok: t} 270 | case token.Buc: 271 | if p.tok == token.Pal { 272 | p.next() 273 | return ast.Rune{ 274 | Tok: t, 275 | Lit: "%=", 276 | Args: append([]ast.Node{ast.Buc{ 277 | Tok: t, 278 | }}, p.consumeWideComma()...), 279 | } 280 | } 281 | return ast.Buc{Tok: t} 282 | case token.Rune: 283 | return p.parseRune(t, lit) 284 | case token.Lus: 285 | p.expect(token.Pal) 286 | q := p.parseExpr() 287 | p.expect(token.Par) 288 | return ast.Rune{ 289 | Tok: t, 290 | Lit: ".+", 291 | Args: []ast.Node{q}, 292 | } 293 | case token.Tis: 294 | p.expect(token.Pal) 295 | q := p.parseExpr() 296 | p.expect(token.Ace) 297 | r := p.parseExpr() 298 | p.expect(token.Par) 299 | return ast.Rune{ 300 | Tok: t, 301 | Lit: ".=", 302 | Args: []ast.Node{q, r}, 303 | } 304 | case token.Pal: 305 | return ast.Rune{ 306 | Tok: t, 307 | Lit: "%-", 308 | Args: p.consumeWide(), 309 | } 310 | case token.Sel: 311 | n := p.parseCons() 312 | p.expect(token.Ser) 313 | return n 314 | default: 315 | panic(fmt.Sprintf("unhandled token %v (%v)", lit, t)) 316 | } 317 | } 318 | 319 | func (p *Parser) parseRune(tok token.Token, lit string) ast.Node { 320 | e, ok := runeTab[lit] 321 | if !ok { 322 | panic("unhandled rune") 323 | } 324 | n := ast.Rune{ 325 | Tok: tok, 326 | Lit: lit, 327 | } 328 | if p.tok == token.Pal { 329 | p.next() 330 | n.Args = p.consumeWide() 331 | } else { 332 | n.Args = make([]ast.Node, e.args) 333 | for i := range n.Args { 334 | p.expect(token.Gap) 335 | n.Args[i] = p.parseExpr() 336 | } 337 | } 338 | if hasArms(lit) { 339 | n.Args = append(n.Args, p.consumeTall(token.HepHep)...) 340 | } else if e.jogging { 341 | n.Args = append(n.Args, p.consumeTall(token.TisTis)...) 342 | } 343 | return n 344 | } 345 | 346 | func (p *Parser) parseCons() ast.Node { 347 | e := p.parseExpr() 348 | if p.tok == token.Ser { 349 | return e 350 | } 351 | p.expect(token.Ace) 352 | return ast.Cell{ 353 | Tok: p.tok, 354 | Head: e, 355 | Tail: p.parseCons(), 356 | } 357 | } 358 | 359 | func New(s *scanner.Scanner) *Parser { 360 | p := &Parser{ 361 | s: s, 362 | } 363 | p.next() 364 | return p 365 | } 366 | -------------------------------------------------------------------------------- /hoon/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "lukechampine.com/urbit/hoon/ast" 8 | "lukechampine.com/urbit/hoon/scanner" 9 | ) 10 | 11 | func TestRoundTrip(t *testing.T) { 12 | var tests = []struct { 13 | prog string 14 | exp string 15 | }{ 16 | { 17 | prog: `[1 2 3]`, 18 | exp: `[1 [2 3]]`, 19 | }, 20 | { 21 | prog: `=/ n 1 22 | n`, 23 | exp: `=/(n 1 n)`, 24 | }, 25 | { 26 | prog: `=/ a 2 27 | =/ b 7 28 | ?: =(a +(b)) 29 | a 30 | b`, 31 | exp: `=/(a 2 =/(b 7 ?:(.=(a .+(b)) a b)))`, 32 | }, 33 | { 34 | prog: `=/ n 1 35 | [. .]:n`, 36 | exp: `=/(n 1 =<([. .] n))`, 37 | }, 38 | { 39 | prog: `=/ n 0 40 | |- 41 | ?: =(n 5) 42 | n 43 | $(n +(n))`, 44 | exp: `=/(n 0 |-(?:(.=(n 5) n %=($ n .+(n)))))`, 45 | }, 46 | { 47 | prog: `|= a=@ 48 | =/ b 2 49 | =/ f |=(@ 7) 50 | (f(a 2, b 3))`, 51 | exp: `|=(a=@ =/(b 2 =/(f |=(@ 7) (%=(f a 2, b 3)))))`, 52 | }, 53 | { 54 | prog: `|= n=@ 55 | =/ acc=@ 1 56 | |- 57 | ?: =(n 0) acc 58 | %= $ 59 | n (dec n) 60 | acc (mul acc n) 61 | ==`, 62 | exp: `|=(n=@ =/(acc=@ 1 |-(?:(.=(n 0) acc %=($ n (dec n), acc (mul acc n))))))`, 63 | }, 64 | { 65 | prog: `=/ x 58 66 | |% 67 | ++ n (add 42 x) 68 | ++ g |= b=@ 69 | (add b n) 70 | --`, 71 | exp: `=/(x 58 |%(++(n (add 42 x)) ++(g |=(b=@ (add b n)))))`, 72 | }, 73 | } 74 | for _, test := range tests[len(tests)-1:] { 75 | var sb strings.Builder 76 | ast.Print(&sb, New(scanner.New([]byte(test.prog))).Parse()) 77 | if got := sb.String(); got != test.exp { 78 | t.Fatalf("bad parse:\nexp: %q\ngot: %q", test.exp, got) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /hoon/scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "strings" 5 | 6 | "lukechampine.com/urbit/hoon/token" 7 | ) 8 | 9 | type Scanner struct { 10 | src []byte 11 | off int 12 | ch rune 13 | } 14 | 15 | func (s *Scanner) next() { 16 | s.off++ 17 | if s.off >= len(s.src) { 18 | s.off = len(s.src) 19 | s.ch = -1 20 | return 21 | } 22 | s.ch = rune(s.src[s.off]) 23 | } 24 | 25 | // TODO: this is currently unused; is it necessary? 26 | func (s *Scanner) peek() rune { 27 | if s.off+1 < len(s.src) { 28 | return rune(s.src[s.off+1]) 29 | } 30 | return -1 31 | } 32 | 33 | func (s *Scanner) scanWhitespace() (token.Token, string) { 34 | var sb strings.Builder 35 | for s.ch == ' ' || s.ch == '\n' { 36 | sb.WriteRune(s.ch) 37 | s.next() 38 | } 39 | lit := sb.String() 40 | if lit == " " { 41 | return token.Ace, lit 42 | } 43 | return token.Gap, lit 44 | } 45 | 46 | func (s *Scanner) scanComment() (token.Token, string) { 47 | var sb strings.Builder 48 | sb.WriteString("::") 49 | for s.ch != '\n' && s.ch != -1 { 50 | sb.WriteRune(s.ch) 51 | s.next() 52 | } 53 | lit := sb.String() 54 | return token.Comment, lit 55 | } 56 | 57 | func isKebab(c rune) bool { 58 | return c == '-' || ('a' <= c && c <= 'z') 59 | } 60 | 61 | func isNumber(c rune) bool { 62 | return c == '.' || ('0' <= c && c <= '9') 63 | } 64 | 65 | func (s *Scanner) scanFace() (token.Token, string) { 66 | var sb strings.Builder 67 | for isKebab(s.ch) { 68 | sb.WriteRune(s.ch) 69 | s.next() 70 | } 71 | return token.Face, sb.String() 72 | } 73 | 74 | func (s *Scanner) scanNumber() (token.Token, string) { 75 | // TODO: this only parses very simple numbers 76 | var sb strings.Builder 77 | for isNumber(s.ch) { 78 | sb.WriteRune(s.ch) 79 | s.next() 80 | } 81 | return token.Num, sb.String() 82 | } 83 | 84 | var runeTab = func() map[int32]string { 85 | m := make(map[int32]string) 86 | for _, r := range strings.Fields(` 87 | .^ .+ .* .= .? 88 | !> !< !: !. != !? !! 89 | =+ =- =| =/ =; =. =: =? =* => =< =~ =, =^ 90 | ?> ?< ?| ?& ?! ?= ?: ?. ?@ ?^ ?~ ?- ?+ 91 | |_ |% |: |. |- |? |^ |~ |= |* |@ 92 | ++ +$ +* +| 93 | :- :_ :+ :^ :* :~ 94 | %~ %- %. %+ %^ %: %= %_ %* 95 | ^| ^& ^? ^: ^. ^- ^+ ^~ ^* ^= 96 | $_ $% $: $? $< $> $- $@ $^ $~ $= 97 | ;: ;+ ;/ ;* ;= ;; ;~ 98 | ~> ~| ~_ ~$ ~% ~< ~+ ~/ ~& ~? ~! ~= 99 | `) { 100 | key := (int32(r[0]) << 16) | int32(r[1]) 101 | m[key] = r 102 | } 103 | return m 104 | }() 105 | 106 | func isComment(c, n rune) bool { 107 | return c == ':' && n == ':' 108 | } 109 | 110 | func isRune(c, n rune) (token.Token, string) { 111 | if lit := runeTab[(c<<16)|n]; lit != "" { 112 | return token.Rune, lit 113 | } 114 | return 0, "" 115 | } 116 | 117 | func isTerminator(c, n rune) (token.Token, string) { 118 | if c == n { 119 | if c == '-' { 120 | return token.HepHep, "--" 121 | } else if c == '=' { 122 | return token.TisTis, "==" 123 | } 124 | } 125 | return 0, "" 126 | } 127 | 128 | var singleCharTokenTab = map[rune]token.Token{ 129 | '|': token.Bar, '\\': token.Bas, '$': token.Buc, 130 | '_': token.Cab, '%': token.Cen, ':': token.Col, 131 | ',': token.Com, '"': token.Doq, '.': token.Dot, 132 | '/': token.Fas, '<': token.Gal, '>': token.Gar, 133 | '#': token.Hax, '-': token.Hep, '{': token.Kel, 134 | '}': token.Ker, '^': token.Ket, '+': token.Lus, 135 | ';': token.Mic, '(': token.Pal, '&': token.Pam, 136 | ')': token.Par, '@': token.Pat, '[': token.Sel, 137 | ']': token.Ser, '~': token.Sig, '\'': token.Soq, 138 | '*': token.Tar, '`': token.Tic, '=': token.Tis, 139 | '?': token.Wut, '!': token.Zap, 140 | } 141 | 142 | func isSingleCharToken(c rune) (token.Token, string) { 143 | if tok, ok := singleCharTokenTab[c]; ok { 144 | return tok, tok.String() 145 | } 146 | return 0, "" 147 | } 148 | 149 | func (s *Scanner) Scan() (token.Token, string) { 150 | switch c := s.ch; { 151 | case c == ' ' || c == '\n': 152 | return s.scanWhitespace() 153 | case 'a' <= c && c <= 'z': 154 | return s.scanFace() 155 | case '0' <= c && c <= '9': 156 | return s.scanNumber() 157 | case c == -1: 158 | return token.EOF, "" 159 | default: 160 | s.next() // always make progress 161 | if isComment(c, s.ch) { 162 | s.next() 163 | return s.scanComment() 164 | } 165 | if tok, lit := isRune(c, s.ch); lit != "" { 166 | s.next() 167 | return tok, lit 168 | } 169 | if tok, lit := isTerminator(c, s.ch); lit != "" { 170 | s.next() 171 | return tok, lit 172 | } 173 | if tok, lit := isSingleCharToken(c); lit != "" { 174 | return tok, lit 175 | } 176 | return token.ILLEGAL, string(c) 177 | } 178 | } 179 | 180 | func New(src []byte) *Scanner { 181 | return &Scanner{ 182 | src: src, 183 | ch: rune(src[0]), 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /hoon/scanner/scanner_test.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "lukechampine.com/urbit/hoon/token" 8 | . "lukechampine.com/urbit/hoon/token" 9 | ) 10 | 11 | func TestScan(t *testing.T) { 12 | tests := []struct { 13 | hoon string 14 | exp []Token 15 | }{ 16 | { 17 | hoon: `=(a +(b))`, 18 | exp: []Token{Tis, Pal, Face, Ace, Lus, Pal, Face, Par, Par}, 19 | }, 20 | { 21 | hoon: `=/ n 1 22 | [. .]:n :: dup`, 23 | exp: []Token{Rune, Gap, Face, Gap, Num, Gap, Sel, Dot, Ace, Dot, Ser, Col, Face, Gap, Comment}, 24 | }, 25 | { 26 | hoon: `|= n=@ 27 | =/ acc=@ 1 28 | |- 29 | ?: =(n 0) acc 30 | %= $ 31 | n (dec n) 32 | acc (mul acc n) 33 | ==`, 34 | exp: []Token{ 35 | Rune, Gap, Face, Tis, Pat, Gap, 36 | Rune, Gap, Face, Tis, Pat, Gap, Num, Gap, 37 | Rune, Gap, 38 | Rune, Gap, Tis, Pal, Face, Ace, Num, Par, Gap, Face, Gap, 39 | Rune, Gap, Buc, Gap, 40 | Face, Gap, Pal, Face, Ace, Face, Par, Gap, 41 | Face, Gap, Pal, Face, Ace, Face, Ace, Face, Par, Gap, 42 | TisTis, 43 | }, 44 | }, 45 | } 46 | for _, test := range tests { 47 | var ts []Token 48 | for s := New([]byte(test.hoon)); ; { 49 | tok, _ := s.Scan() 50 | if tok == token.EOF { 51 | break 52 | } 53 | ts = append(ts, tok) 54 | } 55 | if !reflect.DeepEqual(ts, test.exp) { 56 | t.Fatalf("bad scan:\nexp: %v\ngot: %v", test.exp, ts) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /hoon/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type Token int 8 | 9 | const ( 10 | ILLEGAL Token = iota 11 | EOF 12 | Comment 13 | Ace 14 | Bar 15 | Bas 16 | Buc 17 | Cab 18 | Cen 19 | Col 20 | Com 21 | Doq 22 | Dot 23 | Fas 24 | Gal 25 | Gap 26 | Gar 27 | Hax 28 | Hep 29 | Kel 30 | Ker 31 | Ket 32 | Lus 33 | Mic 34 | Pal 35 | Pam 36 | Par 37 | Pat 38 | Sel 39 | Ser 40 | Sig 41 | Soq 42 | Tar 43 | Tic 44 | Tis 45 | Wut 46 | Zap 47 | HepHep 48 | TisTis 49 | Rune 50 | Face 51 | Num 52 | ) 53 | 54 | var tokens = [...]string{ 55 | ILLEGAL: "ILLEGAL", 56 | EOF: "EOF", 57 | Comment: "COMMENT", 58 | 59 | Ace: "ACE", 60 | Gap: "GAP", 61 | Bar: "|", 62 | Bas: `\`, 63 | Buc: "$", 64 | Cab: "_", 65 | Cen: "%", 66 | Col: ":", 67 | Com: ",", 68 | Doq: `"`, 69 | Dot: ".", 70 | Fas: "/", 71 | Gal: "<", 72 | Gar: ">", 73 | Hax: "#", 74 | Hep: "-", 75 | Kel: "{", 76 | Ker: "}", 77 | Ket: "^", 78 | Lus: "+", 79 | Mic: ";", 80 | Pal: "(", 81 | Pam: "&", 82 | Par: ")", 83 | Pat: "@", 84 | Sel: "[", 85 | Ser: "]", 86 | Sig: "~", 87 | Soq: "'", 88 | Tar: "*", 89 | Tic: "`", 90 | Tis: "=", 91 | Wut: "?", 92 | Zap: "!", 93 | 94 | HepHep: "--", 95 | TisTis: "==", 96 | Rune: "RUNE", 97 | Face: "FACE", 98 | Num: "NUM", 99 | } 100 | 101 | func (t Token) String() string { 102 | s := "" 103 | if 0 <= t && t < Token(len(tokens)) { 104 | s = tokens[t] 105 | } 106 | if s == "" { 107 | s = "token(" + strconv.Itoa(int(t)) + ")" 108 | } 109 | return s 110 | } 111 | -------------------------------------------------------------------------------- /mach/mach.go: -------------------------------------------------------------------------------- 1 | package mach 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | 8 | "lukechampine.com/urbit/hoon/ast" 9 | 10 | "github.com/llir/llvm/ir" 11 | "github.com/llir/llvm/ir/constant" 12 | "github.com/llir/llvm/ir/enum" 13 | "github.com/llir/llvm/ir/types" 14 | "github.com/llir/llvm/ir/value" 15 | ) 16 | 17 | var atomType = types.I32 18 | 19 | func Transpile(n ast.Node) string { 20 | t := newTranspiler() 21 | main := t.module.NewFunc("main", atomType) 22 | entry := main.NewBlock("") 23 | maybeRet(entry, t.expr(subject{}, main, entry, n)) 24 | return t.Finish() 25 | } 26 | 27 | type subject struct { 28 | m map[string]value.Value 29 | } 30 | 31 | func (s subject) get(key string) value.Value { 32 | return s.m[key] 33 | } 34 | 35 | func (s subject) with(key string, val value.Value) subject { 36 | m2 := make(map[string]value.Value, len(s.m)) 37 | for k, v := range s.m { 38 | m2[k] = v 39 | } 40 | m2[key] = val 41 | return subject{m2} 42 | } 43 | 44 | func (s subject) faces() []string { 45 | fs := make([]string, 0, len(s.m)) 46 | for k := range s.m { 47 | fs = append(fs, k) 48 | } 49 | sort.Strings(fs) 50 | return fs 51 | } 52 | 53 | type transpiler struct { 54 | module *ir.Module 55 | id int64 56 | } 57 | 58 | func newTranspiler() *transpiler { 59 | m := ir.NewModule() 60 | return &transpiler{ 61 | module: m, 62 | } 63 | } 64 | 65 | func maybeRet(b *ir.Block, v value.Value) { 66 | if v != nil { 67 | b.NewRet(v) 68 | } 69 | } 70 | 71 | func (t *transpiler) Finish() string { 72 | return t.module.String() 73 | } 74 | 75 | func (t *transpiler) uid() int64 { 76 | id := t.id 77 | t.id++ 78 | return id 79 | } 80 | 81 | func sampleParams(n ast.Node) []*ir.Param { 82 | switch n := n.(type) { 83 | case ast.Tis: 84 | sampleName := n.Left.(ast.Face).Name 85 | var sampleType types.Type 86 | switch n.Right.(type) { 87 | case ast.Pat: 88 | sampleType = atomType 89 | default: 90 | panic("unhandled sample type") 91 | } 92 | return []*ir.Param{ir.NewParam(sampleName, sampleType)} 93 | case ast.Cell: 94 | return append(sampleParams(n.Head), sampleParams(n.Tail)...) 95 | default: 96 | panic("unhandled sample mold") 97 | } 98 | } 99 | 100 | func (t *transpiler) expr(s subject, fn *ir.Func, b *ir.Block, n ast.Node) value.Value { 101 | eval := func(n ast.Node) value.Value { 102 | return t.expr(s, fn, b, n) 103 | } 104 | 105 | switch n := n.(type) { 106 | case ast.Num: 107 | i, _ := strconv.Atoi(n.Int) 108 | return constant.NewInt(atomType, int64(i)) 109 | case ast.Face: 110 | return s.get(n.Name) 111 | case ast.Buc: 112 | return fn 113 | case ast.Rune: 114 | switch n.Lit { 115 | case ".=": 116 | return b.NewICmp( 117 | enum.IPredEQ, 118 | eval(n.Args[0]), 119 | eval(n.Args[1]), 120 | ) 121 | case ".+": 122 | return b.NewAdd( 123 | eval(n.Args[0]), 124 | constant.NewInt(atomType, 1), 125 | ) 126 | case "=/": 127 | face := n.Args[0].(ast.Face) 128 | f := eval(n.Args[1]) 129 | return t.expr(s.with(face.Name, f), fn, b, n.Args[2]) 130 | case "=.": 131 | face := n.Args[0].(ast.Face) 132 | s = s.with(face.Name, eval(n.Args[1])) 133 | return eval(n.Args[2]) 134 | case "?:": 135 | pred := eval(n.Args[0]) 136 | tb := fn.NewBlock("") 137 | fb := fn.NewBlock("") 138 | b.NewCondBr(pred, tb, fb) 139 | maybeRet(tb, t.expr(s, fn, tb, n.Args[1])) 140 | maybeRet(fb, t.expr(s, fn, fb, n.Args[2])) 141 | return nil 142 | case "|-": 143 | // everything in the subject becomes an argument. 144 | var params []*ir.Param 145 | var args []value.Value 146 | var fs subject 147 | for _, face := range s.faces() { 148 | v := s.get(face) 149 | p := ir.NewParam(face, v.Type()) 150 | params = append(params, p) 151 | args = append(args, v) 152 | fs = fs.with(face, p) 153 | } 154 | fn := t.module.NewFunc("", atomType, params...) 155 | fn.SetID(t.uid()) 156 | fn.Linkage = enum.LinkagePrivate 157 | fnb := fn.NewBlock("") 158 | maybeRet(fnb, t.expr(fs, fn, fnb, n.Args[0])) 159 | return b.NewCall(fn, args...) 160 | case "|=": 161 | // everything in the subject becomes an argument, as well as the gate's arguments 162 | var params []*ir.Param 163 | var fs subject 164 | for _, face := range s.faces() { 165 | v := s.get(face) 166 | p := ir.NewParam(face, v.Type()) 167 | params = append(params, p) 168 | fs = fs.with(face, p) 169 | } 170 | sample := sampleParams(n.Args[0]) 171 | params = append(params, sample...) 172 | for _, p := range sample { 173 | fs = fs.with(p.Name(), p) 174 | } 175 | 176 | fn := t.module.NewFunc("", atomType, params...) 177 | fn.SetID(t.uid()) 178 | fn.Linkage = enum.LinkagePrivate 179 | fnb := fn.NewBlock("") 180 | maybeRet(fnb, t.expr(fs, fn, fnb, n.Args[1])) 181 | return fn 182 | case "%-": 183 | switch gate := n.Args[0].(type) { 184 | case ast.Dot: 185 | f := fn 186 | args := make([]value.Value, len(f.Params)-len(n.Args[1:]), len(f.Params)) 187 | for i := range args { 188 | args[i] = s.get(f.Params[i].Name()) 189 | } 190 | for _, a := range n.Args[1:] { 191 | args = append(args, eval(a)) 192 | } 193 | return b.NewCall(f, args...) 194 | case ast.Rune: 195 | f := eval(n.Args[0]).(*ir.Func) 196 | args := make([]value.Value, len(f.Params)-len(n.Args[1:]), len(f.Params)) 197 | for i := range args { 198 | args[i] = s.get(f.Params[i].Name()) 199 | } 200 | for _, a := range n.Args[1:] { 201 | args = append(args, eval(a)) 202 | } 203 | return b.NewCall(f, args...) 204 | case ast.Face: 205 | switch gate.Name { 206 | case "dec": 207 | return b.NewSub(eval(n.Args[1]), constant.NewInt(atomType, 1)) 208 | case "add": 209 | return b.NewAdd(eval(n.Args[1]), eval(n.Args[2])) 210 | case "sub": 211 | return b.NewSub(eval(n.Args[1]), eval(n.Args[2])) 212 | case "mul": 213 | return b.NewMul(eval(n.Args[1]), eval(n.Args[2])) 214 | default: 215 | v := s.get(gate.Name) 216 | if v == nil { 217 | panic(fmt.Sprintf("unknown gate %s", gate.Name)) 218 | } 219 | f := v.(*ir.Func) 220 | if f == nil { 221 | panic(fmt.Sprintf("not a gate: %s", gate.Name)) 222 | } 223 | args := make([]value.Value, len(f.Params)-len(n.Args[1:]), len(f.Params)) 224 | for i := range args { 225 | args[i] = s.get(f.Params[i].Name()) 226 | } 227 | for _, a := range n.Args[1:] { 228 | args = append(args, eval(a)) 229 | } 230 | return b.NewCall(f, args...) 231 | } 232 | default: 233 | panic(fmt.Sprintf("bad gate type %T", gate)) 234 | } 235 | case "%=": 236 | if _, ok := n.Args[0].(ast.Buc); ok { 237 | args := make([]value.Value, len(fn.Params)) 238 | 239 | for i := range args { 240 | // by default, use existing value in subject 241 | args[i] = s.get(fn.Params[i].Name()) 242 | 243 | // if new value provided in centis, use that instead 244 | for j := 1; j < len(n.Args); j += 2 { 245 | face := n.Args[j].(ast.Face) 246 | if face.Name == fn.Params[i].Name() { 247 | args[i] = eval(n.Args[j+1]) 248 | break 249 | } 250 | } 251 | } 252 | return b.NewCall(fn, args...) 253 | } 254 | panic("unhandled centis") 255 | default: 256 | panic("unhandled rune " + n.Lit) 257 | } 258 | default: 259 | panic(fmt.Sprintf("unhandled node type %T", n)) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /mach/mach_test.go: -------------------------------------------------------------------------------- 1 | package mach 2 | 3 | import ( 4 | "testing" 5 | 6 | "lukechampine.com/urbit/hoon/ast" 7 | "lukechampine.com/urbit/hoon/parser" 8 | "lukechampine.com/urbit/hoon/scanner" 9 | ) 10 | 11 | func parse(s string) ast.Node { 12 | return parser.New(scanner.New([]byte(s))).Parse() 13 | } 14 | 15 | func Test(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | hoon string 19 | llvm string 20 | }{ 21 | { 22 | desc: "atom literal", 23 | hoon: `1`, 24 | llvm: ` 25 | define i32 @main() { 26 | 0: 27 | ret i32 1 28 | } 29 | `[1:], 30 | }, 31 | { 32 | desc: "resolve face in subject", 33 | hoon: ` 34 | =/ n 1 35 | n 36 | `, 37 | llvm: ` 38 | define i32 @main() { 39 | 0: 40 | ret i32 1 41 | } 42 | `[1:], 43 | }, 44 | { 45 | desc: "add two faces", 46 | hoon: ` 47 | =/ a 2 48 | =/ b a 49 | (add a b) 50 | `, 51 | llvm: ` 52 | define i32 @main() { 53 | 0: 54 | %1 = add i32 2, 2 55 | ret i32 %1 56 | } 57 | `[1:], 58 | }, 59 | { 60 | desc: "change subject, simple conditional", 61 | hoon: ` 62 | =/ a 2 63 | =/ b 7 64 | =. a 8 65 | ?: =(a +(b)) 66 | a 67 | b 68 | `, 69 | llvm: ` 70 | define i32 @main() { 71 | 0: 72 | %1 = add i32 7, 1 73 | %2 = icmp eq i32 8, %1 74 | br i1 %2, label %3, label %4 75 | 76 | 3: 77 | ret i32 8 78 | 79 | 4: 80 | ret i32 7 81 | } 82 | `[1:], 83 | }, 84 | { 85 | desc: "barhep recursion", 86 | hoon: ` 87 | =/ n 1 88 | =/ acc 1 89 | |- 90 | ?: =(n 6) 91 | acc 92 | $(acc (mul acc n), n +(n)) 93 | `, 94 | llvm: ` 95 | define i32 @main() { 96 | 0: 97 | %1 = call i32 @0(i32 1, i32 1) 98 | ret i32 %1 99 | } 100 | 101 | define private i32 @0(i32 %acc, i32 %n) { 102 | 0: 103 | %1 = icmp eq i32 %n, 6 104 | br i1 %1, label %2, label %3 105 | 106 | 2: 107 | ret i32 %acc 108 | 109 | 3: 110 | %4 = mul i32 %acc, %n 111 | %5 = add i32 %n, 1 112 | %6 = call i32 @0(i32 %4, i32 %5) 113 | ret i32 %6 114 | } 115 | `[1:], 116 | }, 117 | { 118 | desc: "non-tail-recursive barhep", 119 | hoon: ` 120 | =/ n 5 121 | |- 122 | ?: =(n 0) 123 | 1 124 | (mul n $(n (dec n))) 125 | `, 126 | llvm: ` 127 | define i32 @main() { 128 | 0: 129 | %1 = call i32 @0(i32 5) 130 | ret i32 %1 131 | } 132 | 133 | define private i32 @0(i32 %n) { 134 | 0: 135 | %1 = icmp eq i32 %n, 0 136 | br i1 %1, label %2, label %3 137 | 138 | 2: 139 | ret i32 1 140 | 141 | 3: 142 | %4 = sub i32 %n, 1 143 | %5 = call i32 @0(i32 %4) 144 | %6 = mul i32 %n, %5 145 | ret i32 %6 146 | } 147 | `[1:], 148 | }, 149 | { 150 | desc: "recurse without modifying entire subject", 151 | hoon: ` 152 | =/ a 5 153 | =/ b 0 154 | |- 155 | ?: =(a +(b)) b 156 | $(b +(b)) 157 | `, 158 | llvm: ` 159 | define i32 @main() { 160 | 0: 161 | %1 = call i32 @0(i32 5, i32 0) 162 | ret i32 %1 163 | } 164 | 165 | define private i32 @0(i32 %a, i32 %b) { 166 | 0: 167 | %1 = add i32 %b, 1 168 | %2 = icmp eq i32 %a, %1 169 | br i1 %2, label %3, label %4 170 | 171 | 3: 172 | ret i32 %b 173 | 174 | 4: 175 | %5 = add i32 %b, 1 176 | %6 = call i32 @0(i32 %a, i32 %5) 177 | ret i32 %6 178 | } 179 | `[1:], 180 | }, 181 | { 182 | desc: "assign gate to face", 183 | hoon: ` 184 | =/ f 185 | |= a=@ 186 | =/ g 187 | |= b=@ 188 | (dec b) 189 | (g a) 190 | (f 5) 191 | `, 192 | llvm: ` 193 | define i32 @main() { 194 | 0: 195 | %1 = call i32 @0(i32 5) 196 | ret i32 %1 197 | } 198 | 199 | define private i32 @0(i32 %a) { 200 | 0: 201 | %1 = call i32 @1(i32 %a, i32 %a) 202 | ret i32 %1 203 | } 204 | 205 | define private i32 @1(i32 %a, i32 %b) { 206 | 0: 207 | %1 = sub i32 %b, 1 208 | ret i32 %1 209 | } 210 | `[1:], 211 | }, 212 | } 213 | for _, test := range tests { 214 | got := Transpile(parse(test.hoon)) 215 | if got != test.llvm { 216 | t.Errorf("bad transpile:\nhoon: %s\nllvm: %q\ngot: %s", test.hoon, test.llvm, got) 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /ob/ob.go: -------------------------------------------------------------------------------- 1 | package ob 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ed25519" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "math/bits" 13 | "strconv" 14 | "strings" 15 | "unsafe" 16 | 17 | "github.com/spaolacci/murmur3" 18 | "lukechampine.com/urbit/atom" 19 | ) 20 | 21 | type AzimuthPoint uint32 22 | 23 | func (p AzimuthPoint) IsGalaxy() bool { return p < 256 } 24 | func (p AzimuthPoint) IsStar() bool { return 256 <= p && p < 65536 } 25 | func (p AzimuthPoint) IsPlanet() bool { return 65536 <= p } 26 | 27 | func (p AzimuthPoint) ChildStar(i uint8) AzimuthPoint { 28 | if !p.IsGalaxy() { 29 | panic("only galaxies can spawn stars") 30 | } else if i == 0 { 31 | panic("child index must be greater than 0") 32 | } 33 | return p | (AzimuthPoint(i) << 8) 34 | } 35 | 36 | func (p AzimuthPoint) ChildPlanet(i uint16) AzimuthPoint { 37 | if !p.IsStar() { 38 | panic("only stars can spawn planets") 39 | } else if i == 0 { 40 | panic("child index must be greater than 0") 41 | } 42 | return p | (AzimuthPoint(i) << 16) 43 | } 44 | 45 | func (p AzimuthPoint) Parent() AzimuthPoint { 46 | switch { 47 | case p.IsGalaxy(): 48 | panic("galaxies do not have parents") 49 | case p.IsStar(): 50 | return p & 0x000000FF 51 | case p.IsPlanet(): 52 | return p & 0x0000FFFF 53 | } 54 | panic("unreachable") 55 | } 56 | 57 | func (p AzimuthPoint) String() string { 58 | buf := make([]byte, 4) 59 | binary.BigEndian.PutUint32(buf, fein(p)) 60 | return atom.FromBytes(buf).Format("p") 61 | } 62 | 63 | func PointFromName(n string) (AzimuthPoint, error) { 64 | buf := make([]byte, 4) 65 | ok := []bool{true, true, true, true} 66 | switch len(n) { 67 | case 14: // ~dopzod-dopzod 68 | buf[0], ok[0] = phonemeIndex[n[1:4]] 69 | buf[1], ok[1] = phonemeIndex[n[4:7]] 70 | n = n[7:] 71 | fallthrough 72 | case 7: // ~dopzod 73 | buf[2], ok[2] = phonemeIndex[n[1:4]] 74 | n = n[3:] 75 | fallthrough 76 | case 4: // ~zod 77 | buf[3], ok[3] = phonemeIndex[n[1:4]] 78 | default: 79 | return 0, errors.New("invalid length") 80 | } 81 | if !ok[0] || !ok[1] || !ok[2] || !ok[3] { 82 | return 0, errors.New("invalid phoneme") 83 | } 84 | 85 | return fynd(binary.BigEndian.Uint32(buf)), nil 86 | } 87 | 88 | type Comet [16]byte 89 | 90 | func (c Comet) String() string { 91 | parts := make([]interface{}, 8) 92 | for i := range parts { 93 | parts[i] = AzimuthPoint(binary.BigEndian.Uint16(c[i*2:])).String()[1:] 94 | } 95 | return fmt.Sprintf("~%v-%v-%v-%v--%v-%v-%v-%v", parts...) 96 | } 97 | 98 | func (c Comet) Parent() AzimuthPoint { 99 | return AzimuthPoint(binary.BigEndian.Uint16(c[14:])) 100 | } 101 | 102 | func FindComet(star AzimuthPoint) (Comet, string) { 103 | if !star.IsStar() { 104 | panic("not a star") 105 | } 106 | seed := make([]byte, 32) 107 | rand.Read(seed) 108 | pubbuf, secbuf := [65]byte{'b'}, [65]byte{'B'} 109 | for ; ; *(*uint64)(unsafe.Pointer(&seed[0]))++ { 110 | // derive keypair 111 | bits := sha512.Sum512(seed) 112 | cry := ed25519.NewKeyFromSeed(bits[:32]) 113 | sgn := ed25519.NewKeyFromSeed(bits[32:]) 114 | pub := append(append(pubbuf[:1], cry[32:]...), sgn[32:]...) 115 | sec := append(append(secbuf[:1], cry[:32]...), sgn[:32]...) 116 | 117 | // fingerprint 118 | pubsum := sha256.Sum256(pub) 119 | *(*uint32)(unsafe.Pointer(&pubsum)) ^= 0x67696662 120 | h := sha256.Sum256(pubsum[:]) 121 | var c Comet 122 | for i := range c { 123 | c[15-i] = h[i] ^ h[16+i] 124 | } 125 | 126 | if c.Parent() == star { 127 | return c, atom.FromBytes(jamComet(c, sec)).Format("uw") 128 | } 129 | } 130 | } 131 | 132 | func jamComet(who Comet, key []byte) []byte { 133 | // This code is truly shameful. 134 | // Please do not look at it. 135 | 136 | mat := func(a []byte) string { 137 | var buf bytes.Buffer 138 | for _, b := range a { 139 | fmt.Fprintf(&buf, "%08b", b) 140 | } 141 | s := strings.TrimLeft(buf.String(), "0") 142 | switch s { 143 | case "": 144 | return "10" 145 | case "1": 146 | return "1100" 147 | default: 148 | met := bits.Len16(uint16(len(s))) - 1 149 | return s + fmt.Sprintf("%0[1]*b%08b", met, len(s)%(1< 0; j-- { 229 | eff := prf(j-1, uint16(l)) 230 | var tmp uint32 231 | if j%2 != 0 { 232 | tmp = (r + a - eff%a) % a 233 | } else { 234 | tmp = (r + b - eff%b) % b 235 | } 236 | l, r = tmp, l 237 | } 238 | return AzimuthPoint(65536 + a*r + l) 239 | } 240 | 241 | var phonemeIndex = func() map[string]uint8 { 242 | m := make(map[string]uint8) 243 | for i, p := range prefixes { 244 | m[p] = uint8(i) 245 | } 246 | for i, p := range suffixes { 247 | m[p] = uint8(i) 248 | } 249 | return m 250 | }() 251 | 252 | var prefixes = [256]string{ 253 | "doz", "mar", "bin", "wan", "sam", "lit", "sig", "hid", "fid", "lis", "sog", "dir", "wac", "sab", "wis", "sib", 254 | "rig", "sol", "dop", "mod", "fog", "lid", "hop", "dar", "dor", "lor", "hod", "fol", "rin", "tog", "sil", "mir", 255 | "hol", "pas", "lac", "rov", "liv", "dal", "sat", "lib", "tab", "han", "tic", "pid", "tor", "bol", "fos", "dot", 256 | "los", "dil", "for", "pil", "ram", "tir", "win", "tad", "bic", "dif", "roc", "wid", "bis", "das", "mid", "lop", 257 | "ril", "nar", "dap", "mol", "san", "loc", "nov", "sit", "nid", "tip", "sic", "rop", "wit", "nat", "pan", "min", 258 | "rit", "pod", "mot", "tam", "tol", "sav", "pos", "nap", "nop", "som", "fin", "fon", "ban", "mor", "wor", "sip", 259 | "ron", "nor", "bot", "wic", "soc", "wat", "dol", "mag", "pic", "dav", "bid", "bal", "tim", "tas", "mal", "lig", 260 | "siv", "tag", "pad", "sal", "div", "dac", "tan", "sid", "fab", "tar", "mon", "ran", "nis", "wol", "mis", "pal", 261 | "las", "dis", "map", "rab", "tob", "rol", "lat", "lon", "nod", "nav", "fig", "nom", "nib", "pag", "sop", "ral", 262 | "bil", "had", "doc", "rid", "moc", "pac", "rav", "rip", "fal", "tod", "til", "tin", "hap", "mic", "fan", "pat", 263 | "tac", "lab", "mog", "sim", "son", "pin", "lom", "ric", "tap", "fir", "has", "bos", "bat", "poc", "hac", "tid", 264 | "hav", "sap", "lin", "dib", "hos", "dab", "bit", "bar", "rac", "par", "lod", "dos", "bor", "toc", "hil", "mac", 265 | "tom", "dig", "fil", "fas", "mit", "hob", "har", "mig", "hin", "rad", "mas", "hal", "rag", "lag", "fad", "top", 266 | "mop", "hab", "nil", "nos", "mil", "fop", "fam", "dat", "nol", "din", "hat", "nac", "ris", "fot", "rib", "hoc", 267 | "nim", "lar", "fit", "wal", "rap", "sar", "nal", "mos", "lan", "don", "dan", "lad", "dov", "riv", "bac", "pol", 268 | "lap", "tal", "pit", "nam", "bon", "ros", "ton", "fod", "pon", "sov", "noc", "sor", "lav", "mat", "mip", "fip", 269 | } 270 | 271 | var suffixes = [256]string{ 272 | "zod", "nec", "bud", "wes", "sev", "per", "sut", "let", "ful", "pen", "syt", "dur", "wep", "ser", "wyl", "sun", 273 | "ryp", "syx", "dyr", "nup", "heb", "peg", "lup", "dep", "dys", "put", "lug", "hec", "ryt", "tyv", "syd", "nex", 274 | "lun", "mep", "lut", "sep", "pes", "del", "sul", "ped", "tem", "led", "tul", "met", "wen", "byn", "hex", "feb", 275 | "pyl", "dul", "het", "mev", "rut", "tyl", "wyd", "tep", "bes", "dex", "sef", "wyc", "bur", "der", "nep", "pur", 276 | "rys", "reb", "den", "nut", "sub", "pet", "rul", "syn", "reg", "tyd", "sup", "sem", "wyn", "rec", "meg", "net", 277 | "sec", "mul", "nym", "tev", "web", "sum", "mut", "nyx", "rex", "teb", "fus", "hep", "ben", "mus", "wyx", "sym", 278 | "sel", "ruc", "dec", "wex", "syr", "wet", "dyl", "myn", "mes", "det", "bet", "bel", "tux", "tug", "myr", "pel", 279 | "syp", "ter", "meb", "set", "dut", "deg", "tex", "sur", "fel", "tud", "nux", "rux", "ren", "wyt", "nub", "med", 280 | "lyt", "dus", "neb", "rum", "tyn", "seg", "lyx", "pun", "res", "red", "fun", "rev", "ref", "mec", "ted", "rus", 281 | "bex", "leb", "dux", "ryn", "num", "pyx", "ryg", "ryx", "fep", "tyr", "tus", "tyc", "leg", "nem", "fer", "mer", 282 | "ten", "lus", "nus", "syl", "tec", "mex", "pub", "rym", "tuc", "fyl", "lep", "deb", "ber", "mug", "hut", "tun", 283 | "byl", "sud", "pem", "dev", "lur", "def", "bus", "bep", "run", "mel", "pex", "dyt", "byt", "typ", "lev", "myl", 284 | "wed", "duc", "fur", "fex", "nul", "luc", "len", "ner", "lex", "rup", "ned", "lec", "ryd", "lyd", "fen", "wel", 285 | "nyd", "hus", "rel", "rud", "nes", "hes", "fet", "des", "ret", "dun", "ler", "nyr", "seb", "hul", "ryl", "lud", 286 | "rem", "lys", "fyn", "wer", "ryc", "sug", "nys", "nyl", "lyn", "dyn", "dem", "lux", "fed", "sed", "bec", "mun", 287 | "lyr", "tes", "mud", "nyt", "byr", "sen", "weg", "fyr", "mur", "tel", "rep", "teg", "pec", "nel", "nev", "fes", 288 | } 289 | -------------------------------------------------------------------------------- /ob/ob_test.go: -------------------------------------------------------------------------------- 1 | package ob 2 | 3 | import ( 4 | "encoding/hex" 5 | "flag" 6 | "testing" 7 | ) 8 | 9 | func TestComet(t *testing.T) { 10 | var c Comet 11 | hex.Decode(c[:], []byte("1fe49fba73b5725bdb99f904e7acf465")) 12 | if c.String() != "~mirryc-patpex-saldef-padhep--nactyr-sovsev-mosber-bonwet" { 13 | t.Fatal("bad comet name:", c.String()) 14 | } else if c.Parent().String() != "~bonwet" { 15 | t.Fatal("wrong comet parent:", c.Parent().String()) 16 | } 17 | 18 | sk, _ := hex.DecodeString("4230e39bc7a387ec3b9f4f08d68c0ea0093e0bb4ef1f5c618494ae6c7fb4f4e2c2604f698a5e28a63996eb6886d04816188d538864883083d98fa549be5e5bfc55") 19 | jam := formatUW(jamComet(c, sk)) 20 | if jam != "0w2.G~ySL.nOjiN.-P1C4.gOh2D.6z0IA.q4cQt.sIsQN.gLhji.DI65N.uBE~J.Btagz.2K3~v.q1pY4.Q0t6q.MgDPV.TSgZ7.zPv6o.8g7w0.svYA~.tetGV.buTc~.89PRD.EO-M1" { 21 | t.Fatal("bad jam for comet") 22 | } 23 | } 24 | 25 | func TestPoints(t *testing.T) { 26 | nec := AzimuthPoint(1) 27 | if !nec.IsGalaxy() { 28 | t.Error("nec should be a galaxy") 29 | } else if nec.String() != "~nec" { 30 | t.Error("wrong name for point", nec.String()) 31 | } else if ap, err := PointFromName(nec.String()); err != nil || ap != nec { 32 | t.Error("bad point from name:", uint32(ap), err) 33 | } 34 | 35 | marnec := nec.ChildStar(1) 36 | if !marnec.IsStar() { 37 | t.Error("marcnec should be a star") 38 | } else if marnec.Parent() != nec { 39 | t.Error("marnec's parent should be nec") 40 | } else if marnec.String() != "~marnec" { 41 | t.Error("wrong name for point", marnec.String()) 42 | } else if ap, err := PointFromName(marnec.String()); err != nil || ap != marnec { 43 | t.Error("bad point from name:", uint32(ap), err) 44 | } 45 | 46 | ralnyt := marnec.ChildPlanet(1) 47 | if !ralnyt.IsPlanet() { 48 | t.Error("ralnyt-botdyt should be a planet") 49 | } else if ralnyt.Parent() != marnec { 50 | t.Error("ralnyt-botdyt's parent should be marnec") 51 | } else if ralnyt.String() != "~ralnyt-botdyt" { 52 | t.Error("wrong name for point", ralnyt.String()) 53 | } else if ap, err := PointFromName(ralnyt.String()); err != nil || ap != ralnyt { 54 | t.Error("bad point from name:", uint32(ap), err) 55 | } 56 | } 57 | 58 | var testInjective = flag.Bool("injective", false, "run the injectivity test") 59 | 60 | func TestInjectivity(t *testing.T) { 61 | if !*testInjective { 62 | t.Skip("skipping injectivity test") 63 | } 64 | for i := AzimuthPoint(1); i != 0; i++ { 65 | if i%50e6 == 0 { 66 | println(i) 67 | } 68 | if fynd(fein(i)) != i { 69 | t.Fatal("patp not injective") 70 | } 71 | } 72 | } 73 | --------------------------------------------------------------------------------