did:plc:4ujgjsrzaoyeukicod22nq7d
v.0
zero.delve.town, at . 0 sent, follows 0 0 notes
Law
law owner: (request.kind == 0) or (request.subject == "did:plc:4ujgjsrzaoyeukicod22nq7d")
Source vijim-hizih
346 lines of Objective Bend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Plan.obend as Plans
import ./Form.obend as Form
import ./World.obend as World
import ./Card.obend as Card
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Places.obend as Places
import ./Text.obend as Text
type Done = Places.Done
type Letter = Rows.Letter
# A note in the inbox: who it is from (a principal, or the object whose change it tells of),
# their handle when the host knew it, the line, the height its turn read and `n`, the inbox's
# count then. Keyed {at, from, n}, so key order (n first) is the order they came; the inbox
# keeps 64, the host dropping the oldest.
record Note:
from: String
handle: String
text: String
at: Nat
n: Nat
# What the avatar follows: an object and the field it subscribed to (`outbox` for another
# avatar, any field for a thing it watches).
record Followed:
object: Plans.Reference
field: String
# The object a verified principal gets; its id is the principal's DID string. Its principal's
# `send` inserts a Letter into `outbox`, which followers subscribe to; what it follows is
# `following`; what it is told lands in `inbox`.
record State:
handle: String
at: Plans.Reference
holding: Lists.List<Plans.Reference>
inbox: Relations.Relation<Note>
outbox: Relations.Relation<Letter>
following: Lists.List<Followed>
def initial() -> State:
{handle: "", at: Plans.nobody(), holding: Lists.List.nil(), inbox: Relations.empty(), outbox: Relations.empty(), following: Lists.List.nil()}
def relations() -> Relations.Decls:
Lists.append(Lists.append(Relations.Decls.nil(), {field: "inbox", key: Text.words("at from n"), limit: 64n}), {field: "outbox", key: Text.words("at n"), limit: 64n})
# An avatar answers only to its own principal: its id is the principal's DID.
def mine(context: Abi.Context) -> Bool:
context.principal == context.object
# Who the avatar is, as a refusal names it: its handle, else the shortened DID.
def owner(state: State, context: Abi.Context) -> String:
if state.handle == "" then Card.handle(context.object) else state.handle
sum Why:
notMine: {who: String, doing: String}
notAThing: {doing: String}
noAnswer: {from: String}
alreadyFollowing: {object: String}
followingFull: {}
notFollowing: {object: String}
notVisible: {object: String}
noExit: {label: String}
placeHidden: {}
placeSilent: {}
def refusal(w: Why) -> Card.Refusal:
match w:
case notMine(n): {clause: "notMine", reading: "Only {n.who} {n.doing}."}
case notAThing(n): {clause: "notAThing", reading: "Only a thing can be {n.doing}."}
case noAnswer(n): {clause: "noAnswer", reading: "No answer from {n.from}"}
case alreadyFollowing(a): {clause: "alreadyFollowing", reading: "Already following {a.object}"}
case followingFull(_): {clause: "followingFull", reading: "Following thirty-two is the most."}
case notFollowing(n): {clause: "notFollowing", reading: "Not following {n.object}"}
case notVisible(n): {clause: "notVisible", reading: "{n.object} is not yours to follow."}
case noExit(n): {clause: "noExit", reading: "There is no exit named {n.label}"}
case placeHidden(_): {clause: "placeHidden", reading: "The place is not visible."}
case placeSilent(_): {clause: "placeSilent", reading: "The place did not answer."}
def refused(w: Why) -> Done:
Done.refused(refusal(w))
# A refusal the speaker reads: its line, offered, and nothing written.
def told(w: Why) -> Activity<Done>:
Card.tell(refused(w), Document.text("Not done: {refusal(w).reading}\n"))
# --- The inbox ---------------------------------------------------------------------
# A note from the turn's principal (a Tide's tick, an appointment, anyone's line).
def note(state: State, input: {text: String}, context: Abi.Context) -> Activity<Done>:
noted({from: context.principal, handle: context.handle, text: input.text, at: context.height, n: Relations.count(state.inbox)})
def noted(note: Note) -> Activity<Done>:
let written(_) = write {inbox: insert note}
Done.done({})
# --- The mailbox -------------------------------------------------------------------
# One turn may send at most sendsPerTurn (the host's Limits.sendsPerTurn, 32), and the host
# delivers a change to at most that many subscribers a turn (the rest are `unserved`); an
# avatar follows at most that many.
def sendsPerTurn() -> Nat:
32n
sum Mailed:
sent: {n: Nat}
followed: {object: Plans.Reference}
refused: Card.Refusal
def mailRefused(w: Why) -> Mailed:
Mailed.refused(refusal(w))
# The avatar's principal sends a line: it joins the outbox, and the host tells each follower
# (their `mailed`).
def send(state: State, input: {text: String}, context: Abi.Context) -> Activity<Mailed>:
if mine(context) == false then mailRefused(Why.notMine({who: owner(state, context), doing: "sends from it"})) else posted(input.text, context, state)
def posted(text: String, context: Abi.Context, state: State) -> Activity<Mailed>:
let written(_) = write {outbox: insert {handle: context.handle, text: text, at: context.height, n: Relations.count(state.outbox)}}
Mailed.sent({n: Relations.count(state.outbox)})
# Follow another avatar: subscribe to its outbox, delivered to `mailed`.
def subscribe(state: State, input: {to: String}, context: Abi.Context) -> Activity<Mailed>:
following({object: {world: context.world, object: input.to}, field: "outbox"}, context, state)
# Watch any object's field (a bell's `rung`): each change is a note "news from <object>".
def watch(state: State, input: {to: String, field: String}, context: Abi.Context) -> Activity<Mailed>:
following({object: {world: context.world, object: input.to}, field: input.field}, context, state)
def receiver(field: String) -> String:
if field == "outbox" then "mailed" else "news"
def follows(state: State, object: String, field: String) -> Bool:
Lists.any(state.following, fn(f: Followed) -> Bool: f.object.object == object && f.field == field)
def following(f: Followed, context: Abi.Context, state: State) -> Activity<Mailed>:
if mine(context) == false then mailRefused(Why.notMine({who: owner(state, context), doing: "subscribes it"})) else if Lists.any(state.following, fn(g: Followed) -> Bool: Plans.same(g.object, f.object)) then mailRefused(Why.alreadyFollowing({object: f.object.object})) else if Lists.length(state.following) >= sendsPerTurn() then mailRefused(Why.followingFull({})) else subscribing(f)
def subscribing(f: Followed) -> Activity<Mailed>:
match world.subscribe({object: f.object, field: f.field, method: receiver(f.field)}):
case subscribed(_): followedNow(f)
case denied(_): mailRefused(Why.notVisible({object: f.object.object}))
case refused(r): Mailed.refused({clause: r.clause, reading: "{f.object.object} has no {f.field} to follow ({r.clause})."})
def followedNow(f: Followed) -> Activity<Mailed>:
let written(_) = write {following: append f}
Mailed.followed({object: f.object})
def unsubscribe(state: State, input: {to: String}, context: Abi.Context) -> Activity<Mailed>:
if mine(context) == false then mailRefused(Why.notMine({who: owner(state, context), doing: "unsubscribes it"})) else unfollowing(Lists.find(state.following, fn(f: Followed) -> Bool: f.object.object == input.to), input.to)
def unfollowing(found: Lists.Maybe<Followed>, object: String) -> Activity<Mailed>:
match found:
case none(_): mailRefused(Why.notFollowing({object: object}))
case some(s): unsubscribing(s.value)
def unsubscribing(f: Followed) -> Activity<Mailed>:
match world.unsubscribe({object: f.object, field: f.field, method: receiver(f.field)}):
case _: unfollowed(f)
def unfollowed(f: Followed) -> Activity<Mailed>:
let written(_) = write {following: removeItem f}
Mailed.followed({object: f.object})
# Receivers (helpers: only the host delivers to them, from the object followed). A followed
# avatar's letters become notes from it.
def mailed(state: State, input: {object: Plans.Reference, field: String, version: Nat, inserted: Lists.List<Letter>, retracted: Lists.List<Letter>}, context: Abi.Context) -> Activity<Done>:
if context.caller == input.object.object && follows(state, context.caller, "outbox") then letters(input.inserted, Relations.count(state.inbox), context) else Done.done({})
def letters(inserted: Lists.List<Letter>, n: Nat, context: Abi.Context) -> Activity<Done>:
match inserted:
case nil(_): Done.done({})
case cons(c): lettered(c.head, c.tail, n, context)
def lettered(letter: Letter, rest: Lists.List<Letter>, n: Nat, context: Abi.Context) -> Activity<Done>:
let written(_) = write {inbox: insert {from: context.caller, handle: letter.handle, text: letter.text, at: context.height, n: n}}
letters(rest, n + 1n, context)
# Any other field watched: a note that it changed.
def news(state: State, input: World.Changed, context: Abi.Context) -> Activity<Done>:
if context.caller == input.object.object && follows(state, context.caller, input.field) then noted({from: context.caller, handle: "", text: "news from {context.caller}", at: context.height, n: Relations.count(state.inbox)}) else Done.done({})
# --- Holding -------------------------------------------------------------------------
# Things call hold and release on their holder's avatar. Both are idempotent and never
# refuse a thing, so a thing that has already staged its own writes cannot be left half
# done. The thing is the calling object; the giver is the turn's principal, and receiving
# something from somebody else also leaves a note.
def thing(context: Abi.Context) -> Plans.Reference:
{world: context.world, object: context.caller}
def hold(state: State, context: Abi.Context) -> Activity<Done>:
if context.caller == "" then refused(Why.notAThing({doing: "held"})) else if Lists.any(state.holding, fn(h: Plans.Reference) -> Bool: Plans.same(h, thing(context))) then Done.done({}) else held(context, state)
def held(context: Abi.Context, state: State) -> Activity<Done>:
let written(_) = world.write(extend(keep(), {holding: Plans.Entries.append({item: thing(context)}), inbox: if mine(context) then Plans.Entries.keep({}) else Plans.Entries.insert({row: {from: context.principal, handle: context.handle, text: "gave you {context.caller}", at: context.height, n: Relations.count(state.inbox)}})}))
Done.done({})
def release(state: State, context: Abi.Context) -> Activity<Done>:
if context.caller == "" then refused(Why.notAThing({doing: "released"})) else if Lists.any(state.holding, fn(h: Plans.Reference) -> Bool: Plans.same(h, thing(context))) then released(context) else Done.done({})
def released(context: Abi.Context) -> Activity<Done>:
let written(_) = write {holding: removeItem thing(context)}
Done.done({})
# Accept what someone offered: the thing's transfer (judged by its own law) moves custody to
# this avatar, and this avatar records it in its own holding, in the same turn.
def accept(state: State, input: {thing: String}, context: Abi.Context) -> Activity<Done>:
if mine(context) == false then refused(Why.notMine({who: owner(state, context), doing: "accepts for it"})) else accepting({world: context.world, object: input.thing})
def accepting(thing: Plans.Reference) -> Activity<Done>:
match world.call::<Done>({object: thing, method: "transfer", argument: Plans.nothing()}):
case returned(r): accepted(r.result, thing)
case _: refused(Why.noAnswer({from: thing.object}))
def accepted(result: Done, thing: Plans.Reference) -> Activity<Done>:
match result:
case done(_): recorded(thing)
case refused(_): result
def recorded(thing: Plans.Reference) -> Activity<Done>:
let written(_) = write {holding: append thing}
Done.done({})
# --- Moving --------------------------------------------------------------------------
sum Moved:
moved: {to: Plans.Reference}
refused: Card.Refusal
def moveRefused(w: Why) -> Moved:
Moved.refused(refusal(w))
# Walk through an exit of the current place: view it, leave it, enter the next place, and
# record where we are. A refusal before any write leaves nothing changed; the calls into
# places are the only things staged, and a place's refusal is answered as it gave it.
def move(state: State, input: {exit: String}, context: Abi.Context) -> Activity<Moved>:
if mine(context) == false then moveRefused(Why.notMine({who: owner(state, context), doing: "moves it"})) else looked(input.exit, state)
def looked(label: String, state: State) -> Activity<Moved>:
match world.view::<Places.State>({object: state.at}):
case viewed(v): through(label, Lists.find(v.state.exits, fn(e: Places.Exit) -> Bool: e.label == label), state)
case denied(_): moveRefused(Why.placeHidden({}))
case refused(_): moveRefused(Why.placeSilent({}))
def through(label: String, found: Lists.Maybe<Places.Exit>, state: State) -> Activity<Moved>:
match found:
case none(_): moveRefused(Why.noExit({label: label}))
case some(s): leaving(state.at, s.value.to)
def leaving(from: Plans.Reference, to: Plans.Reference) -> Activity<Moved>:
match world.call::<Done>({object: from, method: "leave", argument: Plans.nothing()}):
case returned(r): left(r.result, to)
case _: moveRefused(Why.placeSilent({}))
def left(result: Done, to: Plans.Reference) -> Activity<Moved>:
match result:
case done(_): entering(to)
case refused(r): Moved.refused(r)
def entering(to: Plans.Reference) -> Activity<Moved>:
match world.call::<Done>({object: to, method: "enter", argument: Plans.nothing()}):
case returned(r): entered(r.result, to)
case _: moveRefused(Why.placeSilent({}))
def entered(result: Done, to: Plans.Reference) -> Activity<Moved>:
match result:
case done(_): arrived(to)
case refused(r): Moved.refused(r)
def arrived(to: Plans.Reference) -> Activity<Moved>:
let written(_) = write {at: set to}
Moved.moved({to: to})
# --- The card ------------------------------------------------------------------------
form move:
exit: text 1..64
form note:
text: text 1..280
form accept:
thing: text 1..160
form send:
text: text 1..280
form subscribe:
to: text 1..160
form watch:
to: text 1..160
field: text 1..64
form unsubscribe:
to: text 1..160
# Things call hold and release.
def methods() -> Lists.List<String>:
Text.words("hold release")
def blurb() -> Card.Door:
{word: "avatar", blurb: "You in the world: move through exits, leave notes, follow others and send to your followers."}
# The handle the card shows is a lens, its principal's: `delvetalk <did> set` and `handle: ...`.
def lenses() -> Lists.List<Form.Field>:
Lists.List.cons({head: {name: "handle", kind: Form.Kind.text({min: 1n, max: 64n})}, tail: Lists.List.nil()})
def set(state: State, input: {field: String, value: Form.Value}, context: Abi.Context) -> Activity<Done>:
if mine(context) == false then told(Why.notMine({who: owner(state, context), doing: "changes it"})) else setHandle(Card.valueText(input.value))
def setHandle(handle: String) -> Activity<Done>:
let written(_) = write {handle: set handle}
Card.tell(Done.done({}), Document.text("Set handle to {handle}.\n"))
# Scoped resolution: the avatar's own principal's prose with no spell that starts with an
# action word (`acquire the stone`, `bump counter`, `look`) names its object by the first word
# that is a thing lying in the avatar's place (by its name, then by its id's last segment),
# else an object of that name (a door's: garden, rooms, play); the avatar sends the object
# the matching form as a spell, the rest of the line filling its first field. Several things
# of that name are answered with their ids; `look` shows the place as its reader sees it.
# Anything else is the card default.
def receive(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>:
if mine(context) && Card.quietly(Card.unanswered(input, forms())) then scoped(input, Text.words(firstLine(input.text)), context, state) else answered(input, context, state)
def answered(input: Card.Reply, context: Abi.Context, state: State) -> Activity<Card.Replied>:
Card.answer(input, context, forms(), render(state, context))
def firstLine(text: String) -> String:
textTake(text, textBreak(text, "\n"))
def bare(word: String) -> String:
Card.stripped(word, textLength(word))
def scoped(input: Card.Reply, words: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match words:
case nil(_): answered(input, context, state)
case cons(c): if bare(c.head) == "look" then looking(state) else placing(input, bare(c.head), c.tail, context, state)
def looking(state: State) -> Activity<Card.Replied>:
match world.card({object: state.at}):
case carded(c): Card.tell(Card.Replied.done({action: "look"}), c.document)
case _: Card.tell(Card.Replied.refused({clause: "nowhere", reading: "You are nowhere to look."}), Document.text("You are nowhere to look.\n"))
record Named:
object: Plans.Reference
name: String
record Asked:
input: Card.Reply
action: String
rest: Lists.List<String>
def placing(input: Card.Reply, action: String, rest: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match world.view::<Places.State>({object: state.at}):
case viewed(v): naming({input: input, action: action, rest: rest}, Relations.rows(v.state.things), Lists.List.nil(), context, state)
case _: doors({input: input, action: action, rest: rest}, rest, context, state)
# Each thing lying here by the first line of its card (its name).
def naming(asked: Asked, things: Lists.List<Plans.Reference>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match things:
case nil(_): nouns(asked, asked.rest, named, context, state)
case cons(c): carding(asked, c.head, c.tail, named, context, state)
def carding(asked: Asked, thing: Plans.Reference, things: Lists.List<Plans.Reference>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match world.card({object: thing}):
case carded(c): naming(asked, things, Lists.List.cons({head: {object: thing, name: firstLine(Document.plain(c.document))}, tail: named}), context, state)
case _: naming(asked, things, Lists.List.cons({head: {object: thing, name: ""}, tail: named}), context, state)
def suffix(id: String, word: String) -> Bool:
id == word || textLength(id) > textLength(word) && textDrop(id, textLength(id) - textLength(word) - 1n) == "/{word}"
def matching(named: Lists.List<Named>, word: String) -> Lists.List<Named>:
if Lists.any(named, fn(n: Named) -> Bool: n.name == word) then Lists.filter(named, fn(n: Named) -> Bool: n.name == word) else Lists.filter(named, fn(n: Named) -> Bool: suffix(n.object.object, word))
# The first word naming a thing here decides; the words after it fill the form.
def nouns(asked: Asked, words: Lists.List<String>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match words:
case nil(_): doors(asked, asked.rest, context, state)
case cons(c): chosen(asked, c.tail, matching(named, bare(c.head)), named, context, state)
def chosen(asked: Asked, after: Lists.List<String>, found: Lists.List<Named>, named: Lists.List<Named>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match found:
case nil(_): nouns(asked, after, named, context, state)
case cons(c): if Lists.length(c.tail) > 0n then Card.tell(Card.Replied.refused({clause: "ambiguous", reading: "Which one?"}), Document.text("Which one: {textJoin(Lists.map(found, fn(n: Named) -> String: n.object.object), \", \")}?\n")) else acting(asked, c.head.object, after, context, state)
# Else an object named by a word (the directory's doors are named by their ids).
def doors(asked: Asked, words: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match words:
case nil(_): answered(asked.input, context, state)
case cons(c): doorNamed(asked, {world: context.world, object: bare(c.head)}, c.tail, context, state)
def doorNamed(asked: Asked, door: Plans.Reference, after: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match world.inspect({object: door}):
case inspected(i): if Lists.any(i.methods, fn(f: Form.Form) -> Bool: f.action == asked.action) then sending(asked, door, after, i.methods) else doors(asked, after, context, state)
case _: doors(asked, after, context, state)
def acting(asked: Asked, object: Plans.Reference, after: Lists.List<String>, context: Abi.Context, state: State) -> Activity<Card.Replied>:
match world.inspect({object: object}):
case inspected(i): if Lists.any(i.methods, fn(f: Form.Form) -> Bool: f.action == asked.action) then sending(asked, object, after, i.methods) else Card.tell(Card.Replied.refused({clause: "noAction", reading: "{object.object} does not take {asked.action}"}), Document.text("{object.object} does not take {asked.action}.\n"))
case _: answered(asked.input, context, state)
# The form as a spell sent to the object's receive, under the speaker; its card answers.
def sending(asked: Asked, object: Plans.Reference, after: Lists.List<String>, forms: Lists.List<Form.Form>) -> Activity<Card.Replied>:
match world.send({object: object, method: "receive", argument: {text: spellFor(asked.action, object, textJoin(after, " "), forms), post: asked.input.post}}):
case _: Card.Replied.done({action: asked.action})
def spellFor(action: String, object: Plans.Reference, rest: String, forms: Lists.List<Form.Form>) -> String:
match Lists.find(forms, fn(f: Form.Form) -> Bool: f.action == action):
case some(f): "delvetalk {object.object} {action}{fieldLine(f.value.fields, rest)}"
case none(_): "delvetalk {object.object} {action}"
def fieldLine(fields: Form.Fields, rest: String) -> String:
match fields:
case cons(c): if rest == "" then "" else "\n{c.head.name}: {rest}"
case nil(_): ""
def holdingLine(item: Plans.Reference) -> Document.Document:
Document.text("Holding: {item.object}\n")
def noteLine(item: Note) -> Document.Document:
Document.text("{Card.shown(item.from, item.handle, Card.stranger())}: {item.text}\n")
# Notes newest first; eight of each list, then a count.
def render(state: State, context: Abi.Context) -> Document.Document:
Document.Document.sequence({items: Lists.concat(Lists.concat(Document.Documents.cons({head: Document.text("{state.handle}, at {state.at.object}.\n"), tail: Document.Documents.nil()}), Card.clipped(Lists.map(state.holding, holdingLine), 8n)), Lists.concat(mailFor(state, context), inboxFor(state, context)))})
# What it sent is counted for everyone; what it follows is its principal's to see.
def mailFor(state: State, context: Abi.Context) -> Document.Documents:
Document.Documents.cons({head: Document.text("{natText(Relations.count(state.outbox))} sent, follows {natText(Lists.length(state.following))}\n"), tail: if Card.reads(context.object, context) then Card.clipped(Lists.map(state.following, fn(f: Followed) -> Document.Document: followLine(f, context)), 8n) else Document.Documents.nil()})
def followLine(f: Followed, context: Abi.Context) -> Document.Document:
let watched = if f.field == "outbox" then "" else " ({f.field})"
Document.text("Follows: {Card.name(f.object.object, context)}{watched}\n")
# The notes are the avatar's principal's alone; anyone else sees how many there are.
def inboxFor(state: State, context: Abi.Context) -> Document.Documents:
if Card.reads(context.object, context) then Card.clipped(Lists.map(Lists.reverse(Relations.rows(state.inbox)), noteLine), 8n) else Document.Documents.cons({head: Document.text(if Relations.count(state.inbox) == 1n then "1 note\n" else "{natText(Relations.count(state.inbox))} notes\n"), tail: Document.Documents.nil()})
Ledger
- ◦ createdht.73tajaf-kotilarrive:did:plc:4ujgjsrzaoyeukicod22nq7d · did:plc:6amo7col5h4ciq2gpm5eur7b