RFC 515

Specifications for Datalanguage, Version 0/9

๐Ÿ“… published June 1973, from ietf

RFC 515: Specifications for Datalanguage, Version 0/9 ๐ŸŽ€

  • Network Working Group ๐Ÿ’–
  • Author: R. Winter, Computer Corporation of America ๐Ÿ‘ฉโ€๐Ÿ’ป
  • NIC 16446, dated 6 June 1973 ๐Ÿฆ‹
  • The header doesn't list a category, and it doesn't obsolete or update anything. โœจ

Preface ๐ŸŒธ

okay so Datalanguage is the language the Datacomputer speaks ๐Ÿ—ฃ๏ธ The Datacomputer is a data utility system being built for the Arpanet, and it handles data storage and data management for computers on the network. it's giving infrastructure, bestie ๐Ÿงš

Version 0/9 is already running at CCA ๐Ÿ  ngl it's super primitive, but it's still a great chance to get hands-on with the Datacomputer and the core Datalanguage ideas ๐Ÿ’…

Later versions will bring in more of the full Datalanguage, which was described earlier in Datalanguage, Working Paper No. 3, Datacomputer Project, October, 1971, NIC 8028 ๐Ÿ“š One big limit in 0/9 is that elementary data items must be fixed-length ASCII strings (--). That gets dropped in Version 0/10, which is being implemented right now ๐Ÿ”ฅ

Once everyone has learned from building these early versions and from using them, a revised spec for the full language will come out ๐ŸŒˆ

1. Introduction ๐Ÿ“

this doc is the precise, complete spec for Datalanguage, Version 0/9 ๐Ÿ“‹ It has 11 sections, and this intro is section 1. Section 2 gives the general vibe of what 0/9 can do. Sections 3 and 4 are about data description and the directory. Sections 5 through 8 cover how you express data management operations. Section 9 is about recognising names, and Section 10 is the misc corner. Section 11 has the syntax written out in BNF ๐Ÿง 

There's a user manual coming too ๐Ÿ“– It'll teach the language in tutorial form and cover the parts of the Datacomputer-user interface that aren't the language itself โœจ

2. Capabilities of Version 0/9 ๐Ÿ’–

0/9 can store files, add data to existing files, and delete files ๐Ÿ—‚๏ธ Retrievals can spit out whole files or just subsets of them. You can pick data out of files by its content, using expressions built from boolean and inequality operators ๐Ÿ”

The file creator gets to decide whether an inversion is built and kept up by the Datacomputer ๐Ÿช„ An inversion makes selective retrieval faster, but it costs storage space and file maintenance effort. Everyone who isn't the file creator doesn't need to know the inversion exists, or which fields are inverted file keys ๐Ÿ™ˆ The language lets people just say the result they want, and the Datacomputer uses the inversion as much as the request allows ๐Ÿ‘‘

Elementary data items are fixed-length ASCII strings. Files are a restricted kind of hierarchical structure ๐ŸŒณ

A lot of the limits in this memo are temporary ๐Ÿซถ Anything marked with 3 asterisks (***) is a restriction that'll be heavily loosened or removed completely in the next version of the software ๐Ÿ’€

3. Data Description ๐ŸŽ€

A container is a variable whose value is a data object with a general character and arbitrary size. (In Version 0/9, size is restricted. See section 3.4.) Examples of containers in other systems are files, records, fields, groups, and entries ๐Ÿง

The container is not the same thing as the data inside it ๐Ÿ™Œ For example, allocating space is an operation on a container. Changing the unit price field from 25 to 50 is an operation on the data in a container ๐Ÿ’…

A container can enclose other containers. A container that isn't enclosed by another one is called outermost. If container A encloses container B, and no other container inside A also encloses B, then A immediately encloses B ๐Ÿช†

A Datalanguage description is a statement of a container's properties ๐Ÿ“

Every container has the attributes ident and type ๐Ÿท๏ธ The ident is the character string users use to refer to the container. The type decides what form the container's value takes. The value can be elementary, or it can be made of other containers. There are 3 types: LIST, STRUCT, and STRING(***). A LIST holds a group of containers that all have the same description. A STRUCT holds a group of containers, and each one has its own description. A STRING is a sequence of ASCII characters. A STRING isn't really an elementary item, but in Version 0/9 it gets treated like one ๐Ÿ’œ

Some containers can have other attributes too โœจ An outermost container has a function. The function says whether the container is for storage or for transmission ๐Ÿšš

Size is a meaningful dimension of the container, and what that means depends on the type ๐Ÿ“ It's used for space allocation and for parsing data streams ๐Ÿงฉ

An aggregate container (one that holds other containers) has, as an attribute, the description or descriptions of its components. So if S is a STRUCT containing A, B, and C, then the descriptions of A, B, and C are all attributes of S ๐Ÿซง

A STRING that shows up in certain contexts can have an inversion attribute ๐Ÿ”‘ This is an access property that isn't really local to the STRING. It's linked to it for convenience ๐Ÿ’ซ

3.1 Ident ๐Ÿชช

The ident of a container is made of alphanumeric characters, and the first one has to be alphabetic. It can't be longer than 100 characters ๐Ÿ“

The elements of a STRUCT must have idents that are unique within that STRUCT ๐Ÿ’–

3.2 Function ๐Ÿงš

A container's function is FILE, PORT, or TEMPORARY PORT ๐ŸŽฏ When the function is FILE, the container is used to store data at the Datacomputer. When the function is PORT, the container is used to transmit data into or out of the Datacomputer. TEMPORARY PORT (which can be shortened to TEMP PORT) acts like a PORT, but its description isn't kept in the Datacomputer past the session it was created in โณ

3.3 Type ๐ŸŒˆ

Type is LIST, STRUCT, or STRING. These were defined on the preceding page ๐Ÿ“„

In an occurrence of a STRUCT, the elements show up in the same order their descriptions show up in the STRUCT description. Every element is present in every occurrence of the STRUCT ๐ŸŽ€

An element of a STRUCT or LIST can be a container of any datatype. The outermost container, though, has to be a LIST(***) ๐Ÿ‘‘

3.4 Size ๐Ÿ“

The size of a STRING is how many characters it has. The size of a STRUCT isn't defined (***). The meaning of a LIST's size depends on other properties of that LIST (***) ๐Ÿคทโ€โ™€๏ธ

Usually, a LIST's size is its number of LIST-members. The outermost-LIST is the exception ๐ŸŒŸ For an outermost-LIST with a function of FILE, the size is the number of LIST-members that space should be allocated for. If no size is given in this case, the system works out a default ๐Ÿงฎ For an outermost-LIST with a function of PORT, the size gets ignored (***) ๐Ÿ™ˆ

Only outermost containers can be bigger than a TENEX page (2560 ASCII characters) (***) ๐Ÿ“ฆ

3.5 Inversion ๐Ÿ”

An inversion is an auxiliary data structure that helps with retrieval by content ๐Ÿงฒ

Its main use is fast retrieval of sets of outermost-LIST-members. (This can be extended to other container sets, and it will be after release 1 ๐Ÿš€) Picture a list of weather observations stored as a file on the Datacomputer ๐ŸŒฆ๏ธ If you want to retrieve observations by COUNTRY quickly, you mark that in the description of the COUNTRY container. In information retrieval, that makes COUNTRY a key for retrieving observations ๐Ÿ—๏ธ

Keep in mind that the inversion option only affects how efficient retrieval by COUNTRY is. It doesn't affect whether you can retrieve by COUNTRY at all ๐Ÿ’…

The inversion option has some restrictions. First, it can only be applied to STRINGs. Second, a STRING with the inversion option has to occur only once in each outermost-LIST-member. Third, it gets ignored when it's applied to STRINGs in PORT descriptions ๐Ÿšซ

Eventually there'll be several kinds of inversion option. For now, Version 0/9 only has the 'D' option, which stands for distinct ๐Ÿฆ‹

3.6 Syntax โœ๏ธ

A description is just a list of properties, and they show up in this order:

<ident> <function> <type> <size> <other>

Properties that don't apply get left out. Here's an example:

F FILE LIST (25) A STR (10)

Here 'F' is the <ident>, 'FILE' is the <function>, 'LIST' is the <type>, '(25)' is the size, and 'A STR (10)' is the <other> of this one description. Also, 'A STR (10)' is its own description too: the description for the members of the LIST named F ๐Ÿง 

Here's a full description for a file of weather observations, keyed on location ๐ŸŒ

WEATHER FILE LIST
OBSERVATION STRUCT
     LOCATION STRUCT
           CITY STR (10), I=D
           COUNTRY STR (10), I=D
           END
     TIME STRUCT
           YEAR STR (2)
           DAY STR (3)
           HOUR STR (2)
           END
     DATE STRUCT
           TEMPERATURE STR (3)
           RAINFALL STR (3)
           HUMIDITY STR (2)
           END
     END

The ENDs are there to mark where the list of a STRUCT's elements stops ๐Ÿ `, I=D' says the string is an inversion key for retrieving outermost-LIST-members ๐Ÿ”‘

4. Directory ๐Ÿ“

The directory is a system file that keeps the names and descriptions of all user-defined containers ๐Ÿ—„๏ธ

It's structured as a tree ๐ŸŒฒ Each node has an ident, and idents don't have to be unique. There's exactly one path from the root of the tree to any node. The idents along that path get joined together with periods to make a pathname, and that pathname uniquely identifies the node. For example, A.B.C could be the pathname for a node whose ident is C ๐Ÿ’œ

In a later version of the software, the directory gets generalised to allow links between nodes. That means it won't really be a tree anymore. For now, though, the tree model is convenient and good enough ๐Ÿซถ

A node can represent a container, or it can just hold a spot in the space of pathnames. When a node represents a container, it can't (for now) have subordinate nodes ๐Ÿ™…โ€โ™€๏ธ

Eventually, the plan is to model the directory as a structure of containers, with its description spread throughout that structure ๐Ÿงฉ Most operations defined on the directory will be defined on user data, and the other way around too. Access privileges and privacy locks will be part of the data description, and they'll apply to both directory nodes and the data structures below the node level ๐Ÿ”

4.1 CREATE

Okay bestie, so a CREATE-request does one of two things ๐Ÿ’–. (a) It adds a node to the directory, and you can optionally hook a PORT or a FILE description onto that node ๐ŸŽ€. Or (b) it makes a temporary container that is not entered in the directory. It still has a description, though, and you can reference it in requests โœจ. If the description defines a file, CREATE allocates space for that file ๐Ÿ’พ.

To create a node with a description:

CREATE <pathname> <description> ;

To create a node with no description:

CREATE <pathname> ;

Note that the description decides whether or not the container is temporary (see section 3.2 for details) ๐Ÿงš.

A CREATE-request adds a single node to the directory, so it's giving one step at a time ๐Ÿซถ. To add CCA.RAW.F to an empty directory, you need three requests:

CREATE CCA ;
CREATE CCA.RAW ;
CREATE CCA.RAW.F ;

Notice that the last ident of the pathname doubles as the first ident of the description ๐Ÿ‘€:

CREATE CCA.RAW.G FILE LIST A STR (5) ;

So G is both the ident of a node and the ident of an outermost container of type LIST. Iconic double duty ๐Ÿ‘‘.

4.2 DELETE

A DELETE-request deletes a whole tree of nodes, plus any associated descriptions or data ๐Ÿ—‘๏ธ. The syntax is:

DELETE <pathname> ;

The named node and any subordinates get deleted, no exceptions ๐Ÿ’€.

Heads up though: if you want to delete the data but keep the directory entry and description, DELETE should not be used. Use the proper method in section 6.3 instead ๐Ÿ™ˆ.

4.3 LIST

The LIST-request is how you display system data you care about ๐Ÿ“‹. It sends the data you asked for out through the Datalanguage output port ๐Ÿ’Œ.

Several LIST arguments work on the directory. LIST %ALL transmits all pathnames in the directory ๐ŸŒธ. LIST %ALL.%SOURCE transmits all descriptions in the directory ๐Ÿฆ‹. You can use a pathname in place of %ALL:

LIST <pn>.%ALL

This lists pathnames subordinate to <pn> ๐Ÿ“.

LIST <pn>.%SOURCE

This lists descriptions subordinate to the node represented by <pn> ๐Ÿ’œ.

For details about the LIST-request, see section 10.1 ๐Ÿ“–.

5. Opening and closing containers

Containers must be open before you can operate on them ๐Ÿ”“.

A container is open the moment it's first created โœจ. It stays open until a CLOSE-request closes it explicitly, or a DELETE-request closes it implicitly, or the session ends ๐ŸŒˆ.

A closed container gets opened by an OPEN-request ๐ŸŽ€. A temporary container is always open, and a CLOSE-request deletes it ๐Ÿ’….

5.1 Modes

An open container has a mode, and it's one of these: READ, WRITE, or APPEND ๐Ÿฆ‹. The mode decides the meaning and/or legitimacy of certain operations on the container ๐Ÿ”ฅ.

The operation that opens the container sets the mode ๐Ÿงš. You can change it any time with a MODE-request ๐Ÿ’ซ. A CREATE leaves the container in WRITE mode โœ๏ธ. An OPEN either specifies the mode explicitly or implicitly sets the mode to READ ๐Ÿ‘‘.

5.2 Syntax

To open a container:

OPEN <pathname> <mode> ;

or:

OPEN <pathname> ;

where <mode> is defaulted to READ ๐Ÿผ.

To close a container:

CLOSE <ident> ;

where <ident> is the name of an outermost container ๐Ÿ’–.

Two containers with the same outermost <ident> can not be opened at the same time (***) ๐Ÿ™…โ€โ™€๏ธ.

To change the mode of an open container:

MODE <ident> <newmode> ;

5.3 LIST

LIST %OPEN transmits the name, mode and connection status of each open outermost container through the Datalanguage output port ๐Ÿ“ฎ. (The Datalanguage output port is the destination where all Datacomputer diagnostics and replies get sent ๐Ÿ’Œ. It gets established when the user first connects to the Datacomputer ๐Ÿซถ.) For details of the LIST-request, see section 10.1 ๐Ÿ“š.

6. Assignment

Assignment transfers data from one container to another ๐ŸŽ.

The equal sign ('=') is the symbol for assignment ๐ŸŸฐ. The value of the operand on the right of the equal sign gets transferred to the operand on the left โœจ. (Eventually, both operands will be weakly-restricted Datalanguage expressions, which may evaluate to sets as well as to single containers. For now, the left must be a container name, and the right may be a container name or a constant ๐ŸŒท.)

Assignment is defined for all types of containers ๐Ÿ’œ. When the containers are aggregates, their elements get paired, and data moves between paired elements ๐Ÿงธ. Elements of the target container that don't pair with any source element are handled with a default operation. Currently they get filled with blanks โฌœ.

The operands of an assignment must have descriptions that match ๐Ÿ”. The idea of matching is that the descriptions have to be similar enough that it's obvious how to map one into the other ๐Ÿ—บ๏ธ.

6.1 Conditions for legitimate assignment

Assignment must reference objects, not sets. An object is:

  • (a) an outermost container, or
  • (b) a constant, or
  • (c) in the body of a FOR-loop, either
    • (c1) a member of a set defined by a FOR-OPERAND, or
    • (c2) a container which occurs once in such a member

For a reference of type (c1), the object referenced is taken to be the current member ๐ŸŽฏ. For (c2), the object referenced is whatever occurs in the current member ๐Ÿ’ซ. This is explained further in section 7 ๐Ÿ“–.

The left operand of an assignment has extra restrictions ๐Ÿšง. If it's an outermost container, it must be open in either WRITE- or APPEND-mode. If it's not an outermost container, then the reference is of type (c), which means some FOR-operand has set up a context where the assign-operand is an object. That FOR-operand must be the output-operand of the FOR ๐Ÿง.

When the assign-operand is an outermost container, it must be open. You have to reference that operand by its simple container ident(***), not its directory pathname ๐Ÿ’….

In the body of a loop nested inside one or more other loops, assignments get restricted even further, because of a 0/9 implementation problem. See section 7.2 for details ๐Ÿ™ˆ.

Finally, the descriptions of the operands must match ๐Ÿซถ. If one is a constant, then the other must be a STRING(***). If both are containers, then in the expression:

A = B;

the descriptions of containers A and B match if:

  1. A and B have the same type
  2. If A and B are LISTs, then they have equal numbers of LIST-members, or else A is an outermost-LIST.
  3. If A and B are aggregates, then at least one container immediately enclosed in A matches, and has the same ident as, one container immediately enclosed in B.

6.2 Result of assignment

If the operands are STRINGs, then the value of B, left-justified, replaces the value of A ๐Ÿงต. If B is longer than A, the value gets truncated โœ‚๏ธ. If B is shorter than A, then A gets filled on the right with blanks as necessary ๐Ÿซง.

If the operands are STRUCTs, then assignment is defined in terms of the STRUCT members ๐Ÿงฉ. If a member of A, mA, matches and has the same name as a member of B, mB, then mB is assigned to mA ๐Ÿ’ž. If no such mB exists, then mA gets filled with blanks โฌœ.

If the operands are LISTs, the result depends on several factors ๐ŸŒˆ. First, notice that the descriptions of the LIST-members must match ๐Ÿ‘€. Otherwise, the assignment wouldn't be legitimate by the matching rules of 6.1 ๐Ÿšซ.

If A is an outermost-LIST, it can be in either of two modes: WRITE or APPEND ๐ŸŽ€. If A is in WRITE-mode, its previous contents are first discarded ๐Ÿ—‘๏ธ. Then it's handled as though it were in APPEND-mode ๐Ÿฆ‹.

If A is not an outermost-LIST, then it's always effectively in WRITE-mode ๐Ÿ’ซ.

After taking the mode of A into account, as described above, the procedure is:

  • (a) for each member of LIST B, add a new member to the end of A
  • (b) assign the current number of B to the new member of A

6.3 Deletion of Data Through Assignment

If A is an outermost container in WRITE-mode, and B is a container with a description that matches A, and if B contains no data, then A=B has the effect of deleting all data from A ๐Ÿงน. Note that if A is in APPEND-mode in these circumstances, then A=B is a no-operation (i.e., has no effect) ๐Ÿซ .

7. FOR โœจ

okay so FOR is the loop bestie ๐Ÿ’… the syntax looks like this:

FOR <output set spec>, <input set spec> <body> END ;

the output set is optional, so FOR doesn't have to produce anything. when it's left out, the syntax is:

FOR <input set spec> <body> END ;

the operations in the body run once for each member of the input set. any reference in the body to the input set member just means the current input set member ๐Ÿฆ‹ and when there IS an output set, a brand new member gets created and added to it on every iteration (so, once per input member). references to the output set member work the same way: they mean the current one โœจ

the output set spec has to be the name of a LIST member. each iteration makes one of those and adds it to its LIST (we call that the output LIST). the body sets the value the new member ends up with. any STRING in that new member that the body doesn't give a value to gets the default value of all blanks ๐ŸŽ€

the input set spec has to be an expression that evaluates to a set of LIST-members (more on that in section 7.1 ๐Ÿซถ). each iteration pulls in one of those members, and the FOR wraps up once every member in the set has been processed. the LIST the input members come from is called the input LIST ๐Ÿ’œ

honestly FOR is basically a fancier version of assignment between two LISTs ๐Ÿ’… it's less concise than plain assignment, but it's way more flexible, and it has two big perks:

  • (a) not every input LIST-member has to get copied over. you can pick a subset based on content ๐Ÿ“
  • (b) you get explicit control over which values go into the output LIST-members ๐Ÿ‘‘

this is easiest to see with an example ๐Ÿงš

       P PORT LIST               F FILE LIST
         R STRUCT                  R STRUCT
           B STR                     A STRUCT
           C STR                       A1 STR
           END                         A2 STR
                                     B STR
                                     C STR
                                     END
   (1)   P = F ;
   (2)   FOR P.R, F.R
               P.R = F.R ;
               END ;
   (3)   FOR P.R, F.R WITH A1 EQ 'XY' OR A2 GE 'AB'
               B = C ;
               C = A2 ;
               END

so (1) and (2) are totally the same request ๐Ÿ™Œ but (3) is a whole different vibe. it picks out a subset of the F.Rs, and the values it gives the P.Rs couldn't come out of the matching rules applied to F and P. so it's not expressible as plain assignment ๐Ÿ˜ญ

because FOR is basically assignment between a pair of LISTs, the LISTs it references are mostly held to the same restrictions as assignment. the one exception: the descriptions of the LIST-members don't have to match ๐ŸŒธ the restrictions are:

  • (a) both LISTs have to be objects in the context where the FOR shows up.
  • (b) both LISTs have to be open, or sitting inside open outermost containers.
  • (c) if the output LIST is an outermost container, it has to be in WRITE- or APPEND-mode.
  • (d) if the output LIST isn't outermost, the LIST that most immediately encloses it has to be the output LIST of an enclosing FOR.

the mode of the output LIST changes the result pretty much the way it would in an assignment. a FOR writing to a LIST in WRITE-mode overwrites whatever was there before, while a FOR writing to a LIST in APPEND-mode adds onto the previous contents ๐ŸŒˆ

CAUTION TO THE READER: ok so these specs say stuff like 'LISTs referenced by a FOR' to keep things simple, but that phrase isn't 100% accurate. the operands of a FOR are always LIST members, not LISTs. just keeping it real ๐Ÿ’€

7.1 Details of input set specification

the input set is specified by a Datalanguage expression that evaluates to a set of LIST-members. the expression can be just the set of all members of a LIST, or a subset of them picked out by content ๐Ÿ’– for example, with this description:

    F FILE LIST
         R STRUCT
               A STR (1)
               B STR (2)
               END

the expression:

F.R

references every R on the LIST F. but:

F.R WITH A EQ '5'

only references the R's that contain an A with the value '5' โœจ the expressions allowed as input set specifications look like this:

<list-member-name> WITH <boolexp>

the <boolexp> is built out of comparison expressions joined together with the Boolean operators AND and OR. and any expression can be negated with NOT ๐Ÿซถ

comparison operators have the highest precedence. next highest is AND, then OR, then NOT. the big picture: comparisons bind tightest, NOT is the weakest ๐Ÿงš

the comparison expressions are limited to this form:

<container name> <comop> <constant>

where:

  • (a) the <constant> is a string constant wrapped in single quotes (check section 10.2 for more on constants ๐Ÿ’…)
  • (b) the <comop> is one of six operators:
EQ          equal
NE          not equal
LT          less than
GT          greater than
LE          less than or equal to
GE          greater than or equal to
  • (c) the <container name> is the name of a STRING that shows up once in each LIST-member.

the constant gets truncated or padded with blanks on the right so it's the same size as the container it's being compared to. heads up though: padding on the right isn't always what you want ๐Ÿ˜ฌ users will get control over the padding in a future release. and be careful with numbers in Version 0/9. (a number stored as a STRING should really be described as a number. eventually it'll be possible to do that ๐Ÿ™ˆ)

7.2 FOR-body

okay two operations are legit inside a FOR-body: FOR and assignment ๐ŸŽ€

these are subject to the restrictions from Section 6.1 and from the intro to Section 7. the restrictions boil down to three requirements: (1) the names have to be recognizable (see Section 9 for details), (2) a request has to be consistent about which direction data moves between containers, both within itself and with the MODE of outermost containers, and (3) transfers have to happen between objects, not sets of objects. the first two are permanent, but they'll get weaker in later versions of the language. the last one is temporary and will only show up in early versions ๐Ÿฆ‹

now for a slightly bizarre restriction ๐Ÿ˜ญ it's caused by an implementation problem in Version 0/9, and it hits references made in the body of a loop nested inside another loop. it's not part of the language design and it's not expected to cause real problems for users, but it's here for completeness ๐ŸŒธ

an example makes it click. say we have this description:

    F LIST
          R STRUCT
                A STR (3)
                BL LIST (3)
                      B STR (3)
                C STR (3)
                END

and this request fragment:

    FOR ...,R
          FOR ...,B
                ... = A ;
                ... = C ;
                END
          END

here's what's going on:

  • (a) the outer loop goes through the set of R's in F.
  • (b) for each R in F, the inner loop goes through the set of B's in the BL inside that R.
  • (c) in the inner loop body, there are references to A and C. neither one lives in B, but both live in R. so the objects the inner loop body uses are defined by the outer loop, not the inner loop. usually that's totally fine, but C is where it gets sticky ๐Ÿ™ƒ
  • (d) C shows up beyond the end of BL.

the 0/9 compiler can't do either of two things. it can't look ahead far enough to find C before it compiles the loop's code, and it can't, while generating code to loop over the B's in BL, also generate a separate body of code that skips to the end of BL to find C. so it can handle A, since A was located before loop generation starts, but it can't handle C, which needs a little foresight ๐Ÿ˜ญ

the request fragment wouldn't cause problems if the description were changed to this:

    F LIST
          R STRUCT
                A STR (3)
                C STR (3)
                BL LIST (3)
                      B STR (3)
                END

then both A and C get found before the code for the inner loop is generated โœจ

8. Data Transmission ๐Ÿซถ

data gets moved from container to container by running assignment and FOR operations ๐Ÿ’– the outermost containers involved in a transfer can be files or ports. if both are files, the transfer stays internal to the Datacomputer. if either one is a port, an address out in the external world is needed to make the transmission happen ๐ŸŒ

that address comes from a CONNECT-request, which links a container (one with the PORT function) to an external address:

CONNECT <ident> TO <address> ;

here <address> is either a host and socket number, or a TENEX file designator (for CCA's TENEX) wrapped in single quotes. the host and socket form looks like this:

<socket> AT <host>

where <socket> is a decimal number, and <host> is either a decimal number or a standard host name. (standard host names don't exist yet, so for now it has to be the TENEX 'standard' name for the host. contact the author for the latest list ๐Ÿงš) if <host> is left out, it's taken to be the host the Datalanguage is being sent from ๐Ÿ’…

a port's address can be changed by issuing another CONNECT-request ๐Ÿฆ‹

a DISCONNECT-request just breaks an existing port/address association without setting up a new one. (a CLOSE-request that references an open port runs a DISCONNECT too ๐Ÿ™ˆ) the syntax is:

DISCONNECT <ident> ;

a port is disconnected when: (a) no successful CONNECT-request has ever been issued for it, or (b) a DISCONNECT for the port has run since the last successful CONNECT ๐ŸŒธ

when a disconnected port gets referenced in an assignment, it gets connected by default to one of two things:

  • (a) the connection used to send Datalanguage to the Datacomputer, or
  • (b) the connection used to send Datacomputer diagnostics to the user

which one it picks depends on whether the reference is for input or output ๐Ÿ’œ those connections are set up by the network user's ICP to the Datacomputer at the start of the session โœจ

one more thing: CONNECT and DISCONNECT don't actually open files or network connections ๐ŸŽ€ they just make address associations inside the Datacomputer. the files and connections get opened before each request and closed after each request ๐Ÿซถ

9. Names in Datalanguage โœจ

okay so a name gets recognized once it's been attached to a specific data container (or a whole set of them) ๐Ÿ’…

Datalanguage has mechanisms for recognizing names in context, which means the meaning of a name depends on where it shows up ๐Ÿฆ‹ it's giving main character energy. That's what lets you attach natural meanings to partially qualified names ๐Ÿ’–

For example:

       WEATHER FILE LIST
             STATION STRUCT
                   CITY STR (15)
                   STATE STR (15)
                   DATA LIST (24)
                         OBSERVATION STRUCT
                               HOUR STR (2)
                               TEMPERATURE STR (3)
                               HUMIDITY STR (2)
                               PRESSURE STR (4)
                               END
                   END

       RESULTS PORT LIST
             RESULT STRUCT
                   CITY STR (15)
                   HOUR STR (2)
                   TEMPERATURE STR (3)
                   END

       FOR STATION WITH STATE EQ 'CALIFORNIA'
             FOR RESULT, OBSERVATION WITH HOUR GT '12'
                         AND HUMIDITY LT '75'
                   CITY = CITY ;
                   HOUR = HOUR ;
                   TEMPERATURE = TEMPERATURE ;
                   END ;
             END ;

In the assignment CITY = CITY, the first CITY is understood to be RESULT.CITY and the second is understood to be STATION.CITY ๐ŸŽ€

9.1 Informal Presentation of Recognition Rules

Ident means exactly what it means in section 3. For example, in this description:

F FILE LIST R STRUCT A STR (1) B STR (1) END

F, R, A and B are idents ๐ŸŒธ

A context is a tree whose nodes are idents. In that tree, the terminal nodes are idents of STRINGs. The ident of a LIST is superior to the ident of the LIST-member. The ident of a STRUCT is superior to the idents of the STRUCT elements. The context whose top node is F is said to be the context of F ๐Ÿงš

                  +-----+
                  !  F  !
                  +-----+
                     !
                     !
                  +-----+
                  !  R  !
                  +-----+
                     !
                     !
          +----------+----------+
          !                     !
          !                     !
       +-----+               +-----+
       !  A  !               !  B  !
       +-----+               +-----+

Figure 9.1-1 The context of F

A pathname is a sequence of idents that names the nodes along a path from one node to another. A full pathname in the context starts at the topmost node, so F.R.B is a full pathname in the context of F. A partial pathname starts at some node other than the topmost one (e.g. R.B, B) ๐Ÿ‘‘

In Datalanguage, you can't skip intermediate nodes. Pathnames like F.B (which leaves out R) are not permitted. So partial pathnames are only partial in that extra names get implied on the left ๐Ÿ’…

When recognizing a pathname, PN, in a context, CX, there are three attempts that get made:

  • (a) recognition of PN as a full pathname in CX
  • (b) recognition of PN as a partial pathname in which only the topmost node of CX is omitted
  • (c) recognition of PN as an arbitrary partial pathname occurring only once in CX

The attempts happen in that order, and the recognition process stops at the first one that succeeds ๐Ÿ”ฅ

Here's an example description:

F FILE LIST
      R STRUCT
            A STR
            B STR
            S STRUCT
                  R STR

It defines the context in Figure 9.1-2 ๐Ÿฆ‹

                                  +-----+
                                  !  F  !
                                  +-----+
                                     !
                                     !
                                  +-----+
                                  !  R  !
                                  +-----+
                                     !
                                     !
                    +----------------+----------------+
                    !                !                !
                    !                !                !
                 +-----+          +-----+          +-----+
                 !  A  !          !  B  !          !  S  !
                 +-----+          +-----+          +-----+
                                                      !
                                                      !
                                                   +-----+
                                                   !  R  !
                                                   +-----+

Figure 9.1-2 Example Context

In this context, F.R.A is a full pathname, so it's recognized in attempt (a) โœจ R is a partial pathname where only the topmost node is omitted, so it's recognized in attempt (b). Be careful here: R gets recognized as a reference to F.R, not to F.R.S.R ๐Ÿ™ˆ And finally, B is an arbitrary partial pathname that occurs only once in the context, so it's recognized in attempt (c) ๐Ÿ’œ

There are two stacks of contexts: one for names used in an input sense, and one for names used in an output sense. When a name needs recognizing, you first decide whether the reference is an input reference or an output reference. An input reference is (a) the right hand operand of an assign, or (b) a name in the input set spec of a FOR. An output reference is (a) the left operand of an assign, or (b) the output operand of a FOR. Then you search the first context on the appropriate stack, using the procedure from the previous page. If the name is neither recognized nor ambiguous in that context, the search moves on to the next context on the stack. If the name can't be recognized in any context on the appropriate stack, it's unrecognizable ๐Ÿ˜ญ

When a stack is empty, the recognition procedure changes. The search continues in a special context: the context of %OPEN. Its top node, %OPEN, is a built-in system ident. Subordinate to %OPEN is a context for each open directory node. Each one represents all the idents defined in the directory nodes that have data descriptions:

F FILE LIST R STRUCT A STR (1) B STR (1)

and:

P PORT LIST R STRUCT A STR (1) B STR (1)

Then the context of %OPEN looks like Figure 9.1-3 ๐ŸŒˆ

                                 +-------+
                                 ! %OPEN !
                                 +-------+
                                     !
                                     !
                         +-----------+-----------+
                         !                       !
                         !                       !
                 +-----+                      +-----+
                 !  A  !                      !  S  !
                 +-----+                      +-----+
                    !                            !
                    !                            !
                 +-----+                      +-----+
                 !  R  !                      !  R  !
                 +-----+                      +-----+
                    !                            !
                    !                            !
              +-----+-----+                +-----+-----+
              !           !                !           !
              !           !                !           !
           +-----+     +-----+          +-----+     +-----+
           !  A  !     !  B  !          !  A  !     !  B  !
           +-----+     +-----+          +-----+     +-----+

Figure 9.1-3 The Context of %OPEN

When a directory node is closed, its corresponding context gets removed from the context of %OPEN. When a node is opened, its associated context gets added as the rightmost context subordinate to %OPEN ๐Ÿซถ

9.2 Context Stack Maintenance

The context stacks are always empty between requests. The FOR-operator is what adds entries to the stacks. FOR A adds the context of A to the input context stack. FOR A, B ... adds the context of A to the output stack and the context of B to the input stack ๐Ÿ’–

When you add to an empty stack, FOR adds two contexts instead of one. The second addition is the context of the looparg, and the first addition is the context of the outermost container that encloses the looparg โœจ

For example, say you have a context of %OPEN like Figure 9.1-3, and empty context stacks. The fragment FOR F.R ... adds two contexts: (1) the context of F, and (2) the context of F.R ๐ŸŽ€

Contexts get removed from the stacks by the END that matches the FOR which added them ๐Ÿ’…

10. Miscellaneous Topics โœจ

10.1 The LIST-request

The LIST-request gives the user a way to inspect system data they care about. The user can find out the contents of the directory, the source or parsed form of any data description in the system, and the mode and connection status of any open file or port ๐Ÿ”

The LIST operator has a single operand, which can take any of several forms. The operator's action is to output a list of values on the Datalanguage output port ๐Ÿฆ‹

To output a list of pathnames, the operand used is '%ALL'. When '%ALL' appears alone, all pathnames in the directory get listed. When '%ALL' appears after the last ident in a directory pathname, the full pathnames of all nodes subordinate to the named node get listed ๐ŸŒธ

To output one or more source descriptions, use the operand '.%SOURCE'. '.%SOURCE' has to be preceded by one of (a) '%ALL', (b) '%OPEN', or (c) the ident of an open outermost container. The output is either (a) all descriptions, (b) all open descriptions, or (c) one particular open description ๐Ÿ’œ

To output a parsed description, use the operand '.%DESC' ('%DESCRIPTION' is also accepted). This operand is preceded either by (a) '%OPEN', or (b) the ident of an open outermost container ๐Ÿงš

Examples:

Let P be the ident of an open PORT.  Let A.B.C be a
directory pathname.
    LIST %ALL
    LIST A.B.C.%ALL
    LIST %OPEN
    LIST %ALL.%SOURCE
    LIST %OPEN.%SOURCE
    LIST P.%SOURCE
    LIST %OPEN.%DESC
    LIST P.%DESC

Note that 'LIST A.B.C.%SOURCE' is not legal. '.%SOURCE' must be preceded by the ident of an open container, not a pathname ๐Ÿ™ˆ A similar restriction applies to '.%DESC' ๐Ÿ’€

10.2 Constants in Datalanguage

A constant of type STRING can go in a Datalanguage request by wrapping it in single quotes:

'ABC'

To include a single quote inside a constant, put a double quote in front of it:

'FATHER"'S'

Same deal for double quotes: precede each one with a double quote:

'JOHN SAID ""HELLO""'

These constants can be used on the right side of comparison operators and of assignment operators that reference strings ๐ŸŒˆ

Eventually, Datalanguage is going to have facilities for constants of all datatypes. Those constants are simply a special case of the Datalanguage expression, and they'll be allowed wherever such expressions are allowed ๐Ÿ‘‘

10.3 Character Set โœจ

Okay bestie, so internally, Version 0/9 of the Datacomputer software works in 7-bit ASCII characters ๐Ÿ’–. Its output to the ARPANET gets converted to 8-bit ASCII, and on input from the ARPANET it expects 8-bit characters and converts them down to 7-bit ๐Ÿฆ‹.

To go from 7- to 8-bit, a '0' bit gets prefixed. To go from 8- to 7-bit, the high-order bit gets checked. If the high-order bit is a '0', the bit is discarded and the character is accepted as a 7-bit character. If the high-order bit is a '1', then the character is discarded ๐Ÿ™ˆ.

(Quick reminder, same as everywhere else in this memo: all numerical character codes are in decimal ๐Ÿ”ข.)

The rest of this section is about how codes 0 through 127 get treated when they show up in Datalanguage requests ๐ŸŽ€.

Generally, printing characters are fine in requests, and control characters are not. There are some exceptions, which we'll get into below. The printing characters are codes 32-126, and the control characters are codes 0-31 and 127 โœจ.

Certain control characters are accepted:

  • Tab(9) is accepted freely in requests. It functions as a separator (explained below) ๐ŸŒธ.
  • EOL(31), meaning end-of-line, is accepted in requests. It works as both a separator and an activator (a). EOL has a special meaning in data, so it should not be introduced into STRING constants(***) ๐Ÿšซ.
  • Control-L(12) is an activator and a high-level request delimiter. When it shows up in the input stream, it terminates the processing of any request being handled. It's useful for Datacomputer-user program synchronization ๐Ÿ”ฅ.
  • Control-Z(26) means end-of-session when it's encountered in Datalanguage. It has the properties of Control-L, and on top of that it makes the Datacomputer run an end-of-session procedure. That closes the Datalanguage connections, closes any open files or ports, and so on ๐Ÿ˜ญ. The effect is equivalent to a [LOGOUT(which] does not exist yet).
  • The two-character sequence <carriage return(13), line feed(10)> is equivalent to EOL, and it gets translated to EOL on input from the network. The reverse sequence, as well as either character on its own, is treated simply as other control characters (ignored) ๐Ÿ’….

All other control characters are ignored ๐Ÿ’€.

The printing characters are split further into four groups: special characters, uppercase letters, lowercase letters, and digits. (The membership of these groups is defined in section 11 ๐Ÿ“–.)

Corresponding upper and lower case letters are equivalent in requests, except inside quoted strings ๐Ÿ’œ.

Certain special characters have a lexical function, which is either break or separator. A break character ends the current lexical item and returns itself as the next item. A separator character ends the current item but doesn't begin a new one, so its only job is to separate items. Multiple separators act like a single separator. You can always insert a separator before or after a break character without changing the meaning of the request ๐Ÿงš.

The separators are:

  • tab(9)
  • space(32)
  • end-of-line(31)

The break characters are:

  • left parenthesis(40)
  • right parenthesis(41)
  • equals(61)
  • semicolon(59)
  • period(46)
  • comma(44)
  • quote(39)
  • slash(47)

(a) An activator character makes the Datacomputer process whatever it has received since the previous activator or the beginning of the request. The meaning of a request doesn't depend on whether activators are present. However, a request won't be executed until an activator beyond the end of the request is received โณ.

While Version 0/9 defines (carriage return, linefeed) in terms of EOL, network users should not think in terms of sending EOL's to the Datacomputer over the network. EOL isn't part of the network ASCII character set, and it doesn't have a definite permanent place in Datacomputer implementation plans ๐ŸŒˆ.


10.4 Comments ๐Ÿ’ฌ

Comments can be added to Datalanguage requests. A comment begins with the two-character sequence '/*' and ends with the two-character sequence '*/'. Since slash is a break character, a comment does cause a lexical break, and its overall effect is that of a separator โœจ.

10.5 Reserved Identifiers ๐Ÿšซ

Certain identifiers are reserved in Datalanguage, so you shouldn't use them to name containers or directory nodes. They are:

AND
APPEND
AT
CLOSE
CONNECT
CREATE
DELETE
DISCONNECT
END
EQ
FILE
FOR
GE
GT
LE
LIST
LT
NODE
NE
NOT
OPEN
OR
PORT
READ
STR
STRUCT
TO
WITH
WRITE

More reserved identifiers will be added in Version 0/10 ๐Ÿ‘€.

11. Datalanguage Syntax Expressed in BNF ๐Ÿงฉ

Okay this part is the grammar, so it's super precise. Every line below is copied exactly as it appears in the original ๐Ÿซถ.

11.1 Requests ๐Ÿ“ฌ

      11.1.01  <request>  ::=  ;
      11.1.02  <request>  ::=  <create>
      11.1.03  <request>  ::=  OPEN <pn>  ;
      11.1.04  <request>  ::=  OPEN <pn> <mode>  ;
      11.1.05  <request>  ::=  CLOSE <ident>  ;
      11.1.06  <request>  ::=  CONNECT <ident> TO <address>  ;
      11.1.07  <request>  ::=  DISCONNECT <ident>  ;
      11.1.08  <request>  ::=  MODE <ident> <mode>  ;
      11.1.09  <request>  ::=  DELETE <pn>  ;
      11.1.10  <request>  ::=  LIST <listarg>  ;
      11.1.11  <request>  ::=  <sr-request>  ;

11.2 Data Description and Creation ๐ŸŽจ

      11.2.01  <create>  ::=  CREATE <pn>
      11.2.02  <create>  ::=  CREATE <pn> <ftn> LIST <desc>
      11.2.03  <create>  ::=  CREATE <pn> <ftn> LIST <size> <desc>

      11.2.04  <desc>  ::=  <ident> <attributes>

      11.2.05  <attributes>  ::=  LIST <size> <desc>
      11.2.06  <attributes>  ::=  STRUCT <descs> END
      11.2.07  <attributes>  ::=  STR <size>
      11.2.08  <attributes>  ::=  STR <size> ,I=D

      11.2.09  <descs>  ::=  <desc>
      11.2.10  <descs>  ::=  <descs> <desc>

      11.2.11  <ftn>  ::=  PORT
      11.2.12  <ftn>  ::=  FILE
      11.2.13  <ftn>  ::=  TEMP PORT
      11.2.14  <ftn>  ::=  TEMPORARY PORT

      11.2.15  <size>  ::=  (  <integer constant>  )

11.3 Data Storage and Retrieval ๐Ÿ—„๏ธ

      11.3.01  <sr-request>  ::=  <assign>
      11.3.01  <sr-request>  ::=  <loop>

      11.3.03  <assign>  ::=  <pn> = <object>

      11.3.04  <loop>  ::=  FOR <looparg> <loopbody> END

      11.3.05  <looparg>  ::=  <exp>
      11.3.06  <looparg>  ::=  <pn> , <exp>

      11.3.07  <loopbody>  ::=  <sr-request>
      11.3.08  <loopbody>  ::=  <loopbody1> <sr-request>
      11.3.09  <loopbody>  ::=  <loopbody1>

      11.3.10  <loopbody1>  ::=  <sr-request>  ;
      11.3.11  <loopbody1>  ::=  <loopbody1> <sr-request>  ;

11.4 Expressions ๐Ÿ”

      11.4.01  <exp>  ::=  <pn>
      11.4.02  <exp>  ::=  <pn> WITH <boolexp>

      11.4.03  <boolexp>  ::=  <pn> <comop> <string constant>
      11.4.04  <boolexp>  ::=  (  <boolexp>  )
      11.4.05  <boolexp>  ::=  NOT <boolexp>
      11.4.06  <boolexp>  ::=  <boolexp> AND <boolexp>
      11.4.07  <boolexp>  ::=  <boolexp> OR <boolexp>

      11.4.08  <comop>  ::=  EQ
      11.4.09  <comop>  ::=  NE
      11.4.10  <comop>  ::=  GT
      11.4.11  <comop>  ::=  LT
      11.4.12  <comop>  ::=  GE
      11.4.13  <comop>  ::=  LE

11.5 Miscellaneous ๐ŸŒธ

      11.5.01  <address>  ::=  <quote> <TENEX file designator>
               <quote>
      11.5.02  <address>  ::=  <socket> AT <host>
      11.5.03  <address>  ::=  <socket>

      11.5.04  <socket>  ::=  <integer constant>  //INTERPRETED AS
               DECIMAL

      11.5.05  <host>  ::=  <integer constant>  //INTERPRETED AS
               DECIMAL
      11.5.06  <host>  ::=  *****  TENEX host names *****

      11.5.07  <object>  ::=  <pn>
      11.5.08  <object>  ::=  <string constant>

      11.5.09  <mode>  ::=  READ
      11.5.10  <mode>  ::=  APPEND
      11.5.11  <mode>  ::=  WRITE

      11.5.12  <listarg>  ::=  %ALL
      11.5.13  <listarg>  ::=  <pn>.%ALL
      11.5.14  <listarg>  ::=  %OPEN
      11.5.15  <listarg>  ::=  %ALL.%SOURCE
      11.5.16  [<LISTARG>]  ::=  <IDENT>.%SOURCE
      11.5.17  <listarg>  ::=  %OPEN.%SOURCE
      11.5.18  <listarg>  ::=  %OPEN.%DESC
      11.5.19  <listarg>  ::=  <ident>.%DESC

      11.5.20  <pn>  ::=  <ident>
      11.5.21  <pn>  ::=  <pn>.<ident>

      11.5.22  <ident>  ::=  <letter>
      11.5.23  <ident>  ::=  <ident> <letter>
      11.5.24  <ident>  ::=  <ident> <digit>

      11.5.25  <integer constant>  ::=  <digit>
      11.5.26  <integer constant>  ::=  <integer constant> <digit>

      11.5.27  <string constant>  ::=  <quote> <string conbody>
               <quote>

      11.5.28  <string conbody>  ::=  <nonquote>
      11.5.28  <string conbody>  ::=  <string conbody> <nonquote>

11.6 Character Set ๐ŸŽ€

okay bestie, this is the alphabet section and it's giving very organised ๐Ÿ’… Every character that can show up in datalanguage gets sorted into a bucket: <separator>, <special>, <letter>, <digit>, <nonquote>, <eol>, plus the little guys like <carriage return>, <line feed>, <quote> and <superquote> ๐ŸŒธ The grammar is copied exactly below so nothing gets lost in translation ๐Ÿซถ

      11.6.01  <separator>  ::=   //SPACE(32)
      11.6.02  <separator>  ::=   //TAB(9)
      11.6.03  <separator>  ::=  <eol>

      11.6.04  <special>  ::=  <quote>
      11.6.05  <special>  ::=  <superquote>
      11.6.06  <special>  ::=  <special1>

      11.6.07  <letter>  ::=  A
      11.6.08  <letter>  ::=  B
       ...............
      11.6.09  <letter>  ::=  Z

      11.6.10  <letter>  ::=  a
      11.6.11  <letter>  ::=  b
       ...............
      11.6.12  <letter>  ::=  z

      11.6.13  <digit>  ::=  0
      11.6.14  <digit>  ::=  1
       ...............
      11.6.15  <digit>  ::=  9

      11.6.16  <nonquote>  ::=  <letter>
      11.6.17  <nonquote>  ::=  <digit>
      11.6.18  <nonquote>  ::=  <superquote> <quote>
      11.6.19  <nonquote>  ::=  <superquote> <superquote>
      11.6.20  <nonquote>  ::=  <special1>
      11.6.21  <nonquote>  ::=  <separator>

      11.6.22  <eol>  ::=  //EOL (31)
      11.6.23  <eol>  ::=  <carriage return> <line feed>

      11.6.24  <carriage return>  ::=  //CARRIAGE RETURN (13)
      11.6.25  <line feed>  ::=  //LINE FEED (10)

      11.6.26  <quote>  ::=  ' //SINGLE QUOTE(44)

      11.6.27  <superquote>  ::=  " //DOUBLE QUOTE(34)

      11.6.28  <special1>  ::=  ! //EXCLAMATION POINT(33)
      11.6.29  <special1>  ::=  # //NUMBER SIGN(35)
      11.6.30  <special1>  ::=  $ //DOLLAR SIGN(36)
      11.6.31  <special1>  ::=  % //PERCENT SIGN(37)
      11.6.32  <special1>  ::=  & //AMPERSAND(38)
      11.6.33  <special1>  ::=  ( //LEFT PARENTHESIS(40)
      11.6.34  <special1>  ::=  ) //RIGHT PARENTHESIS(41)
      11.6.35  <special1>  ::=  * //ASTERISK(42)
      11.6.36  <special1>  ::=  + //PLUS SIGN(43)
      11.6.37  <special1>  ::=  , //COMMA(44)
      11.6.38  <special1>  ::=  - //MINUS SIGN(45)
      11.6.39  <special1>  ::=  . //PERIOD(46)
      11.6.40  <special1>  ::=  / //SLASH(47)
      11.6.41  <special1>  ::=  : //COLON(58)
      11.6.42  <special1>  ::=  ; //SEMICOLON(59)
      11.6.43  <special1>  ::=  < //LEFT ANGLE BRACKET(60)
      11.6.44  <special1>  ::=  = //EQUAL SIGN(61)
      11.6.45  <special1>  ::=  > //RIGHT ANGLE BRACKET(62)
      11.6.46  <special1>  ::=  ? //QUESTION MARK(63)
      11.6.47  <special1>  ::=  @ //AT-SIGN(64)
      11.6.48  <special1>  ::=  [ //LEFT SQUARE BRACKET(91)
      11.6.49  <special1>  ::=   //BACK SLASH(92)
      11.6.50  <special1>  ::=  ] //RIGHT SQUARE BRACKET(93)
      11.6.51  <special1>  ::=  ^ //CIRCUMFLEX(94)
      11.6.52  <special1>  ::=  _ //UNDERBAR(95)
      11.6.53  <special1>  ::=  ` //ACCENT GRAVE(96)
      11.6.54  <special1>  ::=  { //LEFT BRACE(123)
      11.6.55  <special1>  ::=  | //VERTICAL BAR(124)
      11.6.56  <special1>  ::=  } //RIGHT BRACE(125)
      11.6.57  <special1>  ::=  ~ //TILDE(126)

      <EOF>


          [ This RFC was put into machine readable form for entry ]
           [ into the online RFC archives by Walter Benton 12/98 ]
๐Ÿ“ฆ where this page comes from