Schemes:Notes
From DocBase
Contents |
FAQ
What is a scheme?
A scheme is a set of definitions and functions (an object!) that handle the operations for a specific kind of port. Ports are made from scheme objects.
Schemes may be written as an interface to just about anything, but are most often used for stream-like interfaces, such as network protocols.
Since they are objects, they can store local data to maintain state.
Actions are used to access ports created from schemes. For example, when you call open or read on a url!, the url is passed to make-port, which looks at the scheme name (the part before ://), creates a port! using the matching scheme definition, and calls the actor func defined in that scheme that matches the original action (open, read, etc.).
How do I create a scheme?
A scheme is defined by calling the make-scheme function. There are a number of basic values you must set for a scheme to function correctly, but you can also add your own words to the scheme objects you create, just as you do with any REBOL object.
The spec you pass to make-scheme also contains "actor" functions that are mapped to port action calls.
How do I create a new scheme?
How do I modify an existing scheme?
?? What do you use as the prototype?
What are the important elements of a scheme?
A scheme doesn't have many fields, but they're all important. All schemes MUST have a name, they SHOULD have a title, most will use the spec and info fields. If you need to respond to events, as in the case of networking schemes that use TCP sub-ports, you will need an awake function; and without actor functions, your scheme won't be able to do much.
There are a large number of actor functions that you MAY include, but your scheme may not need to support them tall. For example, you may choose not to support all the series actions, like insert, append, etc. At the very least, you should include the following actors:
- close
- open
- open?
- read
- write
And possibly also these:
- copy
- create
- delete
- length?
- query
- rename
- update
What is the general architecture of a scheme?
What are the standard scheme-related values in REBOL?
make-scheme
system/standard/port
system/standard/scheme
system/intrinsic/make-port
How does REBOL create ports from schemes?
The creation process is something like this. Given this line:
p: open http://www.rebol.com
This is what happens:
open calls make-port with a url! arg
make-port does the following
- Decodes the url and creates a port object from the scheme
- Sets up port with scheme info
- Calls scheme/init
- Calls scheme/actor/open port
- Returns the port
You can also specify a scheme block directly. e.g.:
p: open [
scheme: 'http
host: "www.rebol.com"
path: none
ref: http://www.rebol.com
]
Geting started
Before you write your first scheme, you should be familiar with the various data structures and functions the system uses when operating on ports. Remember that a scheme defines how a port works. What makes this a little tricky is that your scheme can change the structures of the port, adding words for state, configuration, and data storage; the system calls into your scheme definition when creating the port, and then also passes the port to actor functions.
It's important to keep the separation of scheme and port clear in your mind. A scheme is shared by all ports that use that scheme, so you shouldn't store data that is specific to a port, like the port's state in your scheme's info word for example. A scheme's actor object is shared, but the port spec defined in the scheme is a prototype used to create the spec value for the port, so that can be modified for each port. The awake function for a port will refer to the awake function in the port spec if that value exists, or to the awake function defined in the scheme if not.
See system/intrinsic/make-port to see exactly how this all works.
state / data or your own. You can add your own fields to the port with bind/new in the scheme's init function; it's a bad idea to add them to system/standard/port, since that would likely conflict with other port schemes.
In general, port/state should be the internal state vars for the scheme that users are never supposed to touch directly.
Reference
Objects / Data structures
system/standard/port
make object! [
spec: none
scheme: none
actor: none
awake: none
state: none
data: none
locals: none
]
system/standard/port-spec-head
make object! [
title: none
scheme: none
ref: none
]
system/standard/port-spec-net
make object! [
title: none
scheme: none
ref: none
host: none
port-id: 80
]
system/standard/scheme
make object! [
name: none ; [word!] The URL scheme name. e.g. 'http for an http:// scheme
title: none ; [string!] Human-friendly string; identifies the scheme in lists, errors, etc.
spec: none ; [block! object!] Specification of arguments and options
info: none ; [object!] The object returned from the QUERY action
actor: none ; [block! object!] Port action handlers - action trampoline target functions
awake: none ; [function!] Wake-up event function handler (optional)
; awake: func [event] [...]
; Return true to exit from wait
; Return false to continue waiting
]
- Name
- Is used as the leading part of the scheme. That is, it's the part
that comes before :// in a url. For example, if the name is 'http, you would access the scheme like *http://www.rebol.com*.
- Title
- Is the human-friendly string that identifies the scheme in lists,
errors, etc. It is also put by default in port/spec/title
- Spec
- The spec used to make the actual port spec in system/intrinsics.make-port.
You can omit the spec, in which case either the spec used will be the spec for the scheme name (system/schemes/:name/spec) or system/standard/port-spec-head. Spec can be a prototype object, or a spec block; e.g.:
spec: make system/standard/port-spec-net [
path: %/
timeout: 15
]
- Info
- If a user calls QUERY on the port, this is what they'll get back.
If your scheme is a file-based scheme, e.g. HTTP, it may use system/standard/file-info, but you can return any value you want.
info: system/standard/file-info
info: context [ ; the object returned from the QUERY action
data:
len: none
]
- Actor
- Awake
- Init
- Is not in the standard scheme prototype, but can be specified.
Init is outside of actor and is called by make-port (see system/intrinsic/make-port). If specified, it must be a func that takes a single argument, the port being initialized.
init: func [port] [ ; called for MAKE on PORT (also for direct actions)
print ["Initializing port:" port/spec/ref "with" port/spec/title]
]
; TITLE - in http title - not sure it is used at this point. i guess for reflectivity (ie. show port/scheme/title and port/spec/title to user)
Actors
When REBOL evaluates an action, and the next value is a port, that action is dispatched to an actor func for the port. For example
read http://www.rebol.com
would call the READ actor func for the port opened using the HTTP scheme. If you wrote the HTTP scheme, it would be the READ func defined in the actor block of your HTTP scheme that is called. That means your actor funcs must be compatible with the associated actions that will call them.
series actions: insert, etc.
- create, delete, open, close, read, write, open?, query, update, rename
Atomic actors
Quite often, ports are used to make easy calls like:
read http://www.rebol.com
In this case, the user doesn't have to explicitly open and close the port, which is nice for them; but what does it mean for scheme writers?
REBOL will have created the port before calling the actor, so you can't check to see if the port arg is none. One common approach is to set the port state in the open actor, check that the port state is set in the open? actor, and then simply use open? in the read and write actors. For example:
open: func [port] [
port/state: context [
; more 'state values you need go here
]
port
]
open?: func [port] [
not none? port/state
]
close: func [port] [
port/state: none
port
]
write: func [port data] [
if not open? port [port: open port]
; append port/state/data data ; or something similar
]
read: func [port /part len] [
if not open? port [port: open port]
if none? len [len: length? port/state/data]
; copy/part port/state/data len ; or something similar
]
Note that read and write do not close the port object, even if they opened it. The reason is that calls like
read http://www.rebol.com
don't hold a reference to the port, so it will be garbage collected, which will automatically close it. Of course, if you prefer, you can add the extra code to track whether you opened the port and close it before exiting.
Port Actions
Close
Should return the port.
Create
Delete
Open
Should return the port.
Open?
Should return a logic! value.
Query
Returns the schemes info.
Read
Should return the data read from the port.
Rename
Update
Write
Common return values are the length of the data written or a logic value indicating success or failure.
Series Actions
append
at
back
change
clear
copy
empty?
find
head
head?
index?
insert
length?
modify
next
past?
pick
poke
remove
rm
select
skip
tail
tail?
Functions
make-port
make-scheme
set-scheme
;?? make-scheme
make-scheme: make function! [[
{Make a scheme from a specification and add it to the system.}
def [block!] "Scheme specification"
/with 'scheme "Scheme name to use as base"
/local actor
][
; 'with becomes our prototype scheme object
with: either with [get in system/schemes scheme] [system/standard/scheme]
; Without a prototype, we can't go any further
if not with [cause-error 'access 'no-scheme scheme]
; 'def becomes our scheme object, made from the prototype and the block passed in.
def: make with def
; Every scheme MUST have a name
if not def/name [cause-error 'access 'no-scheme-name def]
;!! Carl to confirm !!
;?? Register the scheme with the system, takes the place of R2 'net-utils/net-install call
set-scheme def
; If 'actor is a block, make an object from it.
if block? :def/actor [
; The actor block must contain valid func specs.
; Allocate enough space in the object to avoid reallocation; the number
; of words should match the number of actor funcs.
; It expects "name: func [args] [body]" format for func defs. Any other
; format will not work.
actor: make object! (length? def/actor) / 4
foreach [name func* args body] def/actor [
name: to-word name
repend actor [name func args body]
]
; Set the actor in our scheme object to reference the actor object
; we just created.
def/actor: actor
]
; Add our scheme object to the list of schemes the system knows about.
;?? How does this relate to set-scheme?
append system/schemes reduce [def/name def]
]]
;>> probe get in system/intrinsic 'make-port
make function! [[
spec [file! url! block! object! word! port!] "port specification"
/local name scheme port
][
case [
file? spec [
name: pick [dir file] dir?/any spec
spec: join [ref:] spec
]
url? spec [
; make url! into spec block:
; http://www.rebol.com
; becomes:
; [scheme: 'http host: "www.rebol.com" path: none ref: http://www.rebol.com]
spec: repend decode-url spec [to-set-word 'ref spec]
name: select spec to-set-word 'scheme
]
block? spec [
name: select spec to-set-word 'scheme
]
object? spec [
name: get in spec 'scheme
]
word? spec [
name: spec
spec: []
]
port? spec [
name: port/scheme/name
spec: port/spec
]
true [
return none
]
]
if not all [
word? name
scheme: get in system/schemes name
] [cause-error 'access 'no-scheme name]
port: make system/standard/port []
port/spec: make any [scheme/spec system/standard/port-spec-head] spec
port/spec/scheme: name
port/scheme: scheme
port/actor: get in scheme 'actor
port/awake: any [get in port/spec 'awake :scheme/awake]
if not port/spec/ref [port/spec/ref: spec]
if not port/spec/title [port/spec/title: scheme/title]
port: to port! port
if in scheme 'init [scheme/init port]
port
]]
>> probe p: system/intrinsic/make-port 'tcp
make port! [
spec: make object! [
title: "TCP Networking"
scheme: 'tcp
ref: []
host: none
port-id: 80
]
scheme: make object! [
name: 'tcp
title: "TCP Networking"
spec: make object! [
title: none
scheme: none
ref: none
host: none
port-id: 80
]
info: make object! [
local-ip: none
local-port: none
remote-ip: none
remote-port: none
]
actor: make native! [[]]
awake: make function! [[event][print ['TCP-event event/type] true]]
]
actor: make native! [[]]
awake: make function! [[event][print ['TCP-event event/type] true]]
state: none
data: none
locals: none
]
Schemes
TCP UDP
HTTP HTTPS/SSL
FTP TFTP SFTP
POP3 IMAP SMTP ESMTP
LDAP WEBDAV CAL
DNS Echo Finger Whois, nickname Gopher Daytime NTP NNTP nameserv, WINS SGMP SNMP IRC
Telnet SSH
APP PPTP
MySQL
ICQ Jabber AIM Yahoo! Messenger
SVN
File schemes
zip, rar, etc. tar png mjpg wmv mpg
