Schemes Notes
Schemes > Notes
Ports access external series such as files, networks, consoles, events, databases, data encoders, and data decoders.
In Rebol 3.0, the port system has be completely redesigned to make it simpler, faster, and easier to use. Substantial effort has been made to remove the majority of the complex internal mechanisms and replace it with a smart streamlined system.
The diagram below shows the basic relationship between schemes, ports, requests, and devices:
Definitions:
- Scheme
- specifies a type of port, such as file, TCP, event, sound, etc. The scheme creates a port, which is:
- Port
- a specific instance of a scheme. For example, when you read a file, the scheme is used to create a port that accesses the file and its data. Each open file has its own port.
- Request
- is a lower level (C) structure used to communicate between a port and a device. In the case of a file, it passes fields like the file name, date, size, and data to be transferred.
- Device
- is native C code that implements a standard set of access methods for a port. Example methods include open, close, read, write, create, and delete. This level is where file or network I/O actually happens.
The primary benefit of this design is that it formalizes a method for accessing a wide range of devices without expanding the Rebol-to-environment API for each device. In a way, it implements a mini operating system to keep Rebol as system-independent as possible.
A scheme specifies a type of port.
The name scheme comes from the description of a URI (URL):
http://host/path
Here HTTP is the scheme name in the URL.
A variety of schemes are built-into Rebol 3 and can be used immediately on boot.
Here is a partial list of built-in schemes:
Scheme | Description |
system | System port (primary event dispatcher) |
stdio | Standard input and output |
stderr | Standard error |
console | Rebol interactive console |
file | Local file access |
dir | Local file directory access |
event | GUI event handling |
DNS | Domain name lookup |
TCP | Transfer control protocol |
UDP | User datagram protocol |
HTTP | Hypertext transfer protocol |
shell | External shell commands (call) |
serial | Serial port access |
USB | USB serial port access |
mutex | Mutual exclusion locks |
More (such as FTP, SMTP, POP) will be included as time and user-base contributions permit.
To list the current schemes:
print words-of system/schemes
or:
foreach val get system/schemes [print [val/name val/title]]
The scheme object is defined in system/standard as:
scheme: context [
name: ; word of http, ftp, sound, etc.
title: ; user-friendly title for the scheme
spec: ; custom spec for scheme (if needed)
info: ; prototype info object returned from query
actor: ; standard action handler for scheme port functions
awake: ; standard awake handler for this scheme's ports
none
]
Where:
- name
- is the type of scheme. This is the word used in the first part of the URL, such as TCP, HTTP, FTP, etc.. It must be a valid Rebol word (e.g. cannot begin with a number).
- title
- is the title of the scheme for user identification and documentation purposes.
- spec
- an object used to specify additional features of the scheme.
- info
- an prototype object that is instantiated for a query on the scheme.
- actor
- the handler function that processes standard actions a port of this scheme type. For example, open, close, read, write, etc.
- awake
- a callback function that is invoked when new events arrive for a port of this scheme type.
The make-scheme function creates a new scheme from a block object specification.
(show example)
A port is an active instance of a scheme.
For example, when you open a file, the system creates a port from the file scheme. The port keeps track of the I/O for that specific file, as well as specific information about the file, such as date, size, and current index position.
The Rebol 3 design for ports is more streamlined than the prior version - providing faster access and with less memory.
The standard port object has been substantially reduced in size and complexity from Rebol 2. The new object is defined by system/standard as:
port: context [
spec: ; published specification of the port
scheme: ; scheme object used for this port
actor: ; port action handler (script driven)
awake: ; port awake function (event driven)
state: ; internal state values (private)
data: ; data buffer (usually binary or block)
none
]
Where:
- spec
- is the object used to define the port. It holds the scheme name and other fields, such as the host for network ports. The format of the spec object depends on the scheme of the port.
- scheme
- points back to the scheme object for this port. It is normally used by lower layers that may need to check scheme related values.
- actor
- a function that is called for handling port actions. Normally, it is the same as that of the scheme, but it can be unique to a specific port. This function can be a native or mezzanine.
- awake
- a callback function called when new events arrive for the port. This is normally the same as that of the scheme, but but it can be unique to a specific port.
- state
- private state data. This is an object or a C-structure in the case of native port schemes. Scripts should not access the state! The format and definition of the state is allowed to change between versions.
- data
- a buffer that is used for the most common types of ports (those that transfer data).
Specific types of ports (schemes) are allowed to extend this structure for their own purposes, but developers should avoid unnecessary fields (bloat).
Make note about return value. A TRUE will satisfy a WAIT, but other values do not.
notes about system port as primary dispatcher
covered in separate document
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.).
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.
?? What do you use as the prototype?
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
- copy
- create
- delete
- length?
- query
- rename
- update
make-scheme
system/standard/port
system/standard/scheme
system/intrinsic/make-port
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
p: open [
scheme: 'http
host: "www.rebol.com"
path: none
ref: http://www.rebol.com
]
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.
make object! [
spec: none
scheme: none
actor: none
awake: none
state: none
data: none
locals: none
]
make object! [
title: none
scheme: none
ref: none
]
make object! [
title: none
scheme: none
ref: none
host: none
port-id: 80
]
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
- Title
- Is the human-friendly string that identifies the scheme in lists,
- Spec
- The spec used to make the actual port spec in system/intrinsics/make-port.
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.
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: func [port] [ ; called for MAKE on PORT (also for direct actions)
print ["Initializing port:" port/spec/ref "with" port/spec/title]
]
- Title
- not sure it is used at this point. i guess for reflectivity (ie. show port/scheme/title and port/spec/title to user)
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
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.
Should return the port.
Should return the port.
Should return a logic! value.
Returns the schemes info.
Should return the data read from the port.
Common return values are the length of the data written or a logic value indicating success or failure.
- 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
]
- 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
- zip, rar, etc.
- tar
- png
- mjpg
- wmv
- mpg