The purpose of the S Language is to create computer programs that are spookily adept at manipulating natural language, whether individual chatterbots or whole story-spaces.  It reached the alpha stage in 2002 and hasn't advanced much since.  It's an amusing little program, which will let almost anyone create "mad-libs" of every kind: random sentences, passages, poems; etc.  It also offers a pattern-matching syntax that is more powerful than the AIML standard for chatterbots, last I checked.

What the S Language lacks is a facility for posting programs online (kind of important for chatterbots), and for sophisticated processing the syntax gets a little involved.  Fairly recently (2013), I redesigned the language on paper with a streamlined syntax that eliminates the notion of a "PATTERNGROUP."  My hope is that the resulting language is clean enough to be authored via visual blocks (see Game Blocks), so that computer-based storytelling and language becomes something everyone can play with.

In the meantime, you're invited to write S programs with the existing parser, and if you're a programmer and want to improve the parser, you're invited to do that as well.

Download the parser and code.  Open Insult.s or Legacy.s to see examples.

The full documentation of the language appears at the bottom of this page.

 

 

 

Story Language


The S language facilitates the creation of randomized grammars and semantic structures. At its simplest, it merely "picks" grammars and words out of lists to generate random prose. At its most elaborate, it can handle state, branching, inheritance, and pattern-matching in ways that might be useful for true "storytelling" or chatterbot implementation.

What follows is a pretty raw listing of the programming constructs currently available in the S language. The descriptions will probably make more sense if you first take a quick glance at a simple S program. The S viewer should have come with a program called Insult.s, located in the "StoryFiles" folder. Look at the program code in a text editor, read the comments, then load and run the program with the S Viewer. This should get you up to speed right away.

 

A. SYNTAX

A word should be said about syntax. The S Language is very loose about handling references to objects. Anything and everything is stored as space-delimited strings. Therefore, it is up to the programmer to prefix his symbolic names in a consistent manner.

Though S code is preprocessed into space-delimited tokens, you do not need to add all of these spaces yourself. In general, punctuation characters do not need to be delimited with spaces.

EXAMPLE:

This is a w_noun.

Note that there is no space before the period. Equivalently, the programmer could have written:

This is a w_noun .

Either way, the S language stores the period as a separate token.

By default, S code is presumed to be normal language. When a "special" name is encountered, like w_noun above, a substitution is made. The WRITE command will recursively evaluate an S expression and then tidy up the punctuation to make sure the resulting string conforms to the standard conventions for English prose.

EXAMPLE:

He felt so p_hero {description sad} .

might generate:

He felt so blue.

"p_hero" is a kind of function call. Note that the presence or lack of spaces around the period and braces has no effect on how the final sentence is written.

 

B. WORDGROUP <listname>

WORDGROUP, the most basic construct available, defines a list of words and associates this list with a symbolic name. Each word must appear on a separate line. Use a blank line to terminate the list. For readability, the name of the list should be preceded by "w_".

EXAMPLE:
WORDGROUP w_color
red
blue
green
yellow

The name of the list can then be used in any S language construct. For instance,
The sky is w_color .
would generate random sentences like "The sky is blue.", "The sky is green."; etc.

Within the WORDGROUP construct, there are three special directives available. The first two, _noun and _verb, let you specify that a word belongs to a particular part of speech. Typically, if you use one of these in a wordgroup, every word in that list should be defined with the same directive. Mix parts of speech only if you aspire to be the John Cage of literature.

_noun <singular> <plural>
_verb <type> <infinitive> <root> <first_present> <first_past> <second_present> <second_past> <third_present> <third_past> <plural_present> <plural_past> <past_perfect> <participle>

The scary list of verb conjugations corresponds, in order, to the pronouns I, you, he, and they/we. Using the continuation character (\) can help make a verb definition more readable. The <type> parameter is one of the following: LINKING, TRANSITIVE, INTRANSITIVE.

EXAMPLES:

_noun dog dogs

_verb LINKING \
to_be be \
am was \
are were \
is was \
are were \
been being

_verb TRANSITIVE \
to_love love \
love loved \
love loved \
loves loved \
love loved \
loved loving

By defining words as nouns and verbs, you can make use of several constructs that the system generates automatically. Here is a list of the constructs. Their specific uses will be clear once you've read about maps, patterngroups, and other S-language features. For now, skim this section.

First, the system generates new wordlists and maps that use the wordgroup name as a root and which correspond to different tenses or word-forms.

EXAMPLE:

This wordgroup of nouns:

WORDGROUP w_hate_noun
_noun hatred hatreds
_noun dislike dislikes

would generate the following objects automatically:

w_hate_noun - contains the singular forms of the nouns.
w_hate_noun_plural - contains the plural forms of the nouns in w_hate_noun.
m_hate_noun - a "map" for matching either plural or singular nouns in w_hate_noun and returning the singular form.

Similarly, _verb generates quite a few objects of its own.

EXAMPLE:

This wordgroup of verbs:

WORDGROUP w_hate_verb
_verb TRANSITIVE to_hate hate (...) // yodda yodda
_verb TRANSITIVE to_despise despise (...) // yodda yodda

would generate the following objects automatically:

w_hate_verb_root - a wordgroup of all root verbs in w_hate_verb
w_hate_verb_infinitive - a wordgroup of all infinitive forms of verbs in w_hate_verb
w_hate_verb_present - a wordgroup of all present forms of verbs in w_hate_verb
w_hate_verb_past - a wordgroup of all past forms of verbs in w_hate_verb
w_hate_verb_first_present - a wordgroup of all first-person present forms of verbs in w_hate_verb
w_hate_verb_first_past - a wordgroup of all first-person past forms of verbs in w_hate_verb
w_hate_verb_second_present - a wordgroup of all second-person present forms of verbs in w_hate_verb
w_hate_verb_second_past - a wordgroup of all second-person past forms of verbs in w_hate_verb
w_hate_verb_third_present - a wordgroup of all third-person present forms of verbs in w_hate_verb
w_hate_verb_third_past - a wordgroup of all third-person past forms of verbs in w_hate_verb
w_hate_verb_plural_present - a wordgroup of all plural present forms of verbs in w_hate_verb
w_hate_verb_plural_past - a wordgroup of all plural past forms of verbs in w_hate_verb
w_hate_verb_past_perfect - a wordgroup of all past-perfect forms of verbs in w_hate_verb
w_hate_verb_participle - a wordgroup of all participial forms of verbs in w_hate_verb

m_hate_verb - a "map" for matching any verb tense in w_hate_verb and "returning" the root form of the verb.

Clearly, you have to be pretty careful how you name wordgroups.

In addition to objects created specifically for a given wordgroup, the system adds words to the following top-level objects.

w_noun - a wordgroup of all singular nouns
w_noun_plural - a wordgroup of all plural nouns

m_noun_form - a "map" for matching any plural or singular noun and "returning" the singular form.

w_verb - a wordgroup of all root verbs
w_verb_infinitive - a wordgroup of all infinitive forms of verbs
w_verb_present - a wordgroup of all present forms of verbs
w_verb_past - a wordgroup of all past forms of verbs
w_verb_first_present - a wordgroup of all first-person present forms of verbs
w_verb_first_past - a wordgroup of all first-person past forms of verbs
w_verb_second_present - a wordgroup of all second-person present forms of verbs
w_verb_second_past - a wordgroup of all second-person past forms of verbs
w_verb_third_present - a wordgroup of all third-person present forms of verbs
w_verb_third_past - a wordgroup of all third-person past forms of verbs
w_verb_plural_present - a wordgroup of all plural present forms of verbs
w_verb_plural_past - a wordgroup of all plural past forms of verbs
w_verb_past_perfect - a wordgroup of all past-perfect forms of verbs
w_verb_participle - a wordgroup of all participial forms of verbs

m_verb_form - a "map" for matching any verb tense and "returning" the root form of the verb.
m_linking_verb_form - a "map" for matching any linking verb tense and "returning" the root form of the verb.
m_transitive_verb_form - a "map" for matching any transitive verb and "returning" the root form of the verb.
m_intransitive_verb_form - a "map" for matching any intransitve verb and "returning" the root form of the verb.

g_verb_first_present_progressive - grammar for matching multiword verb tense
g_verb_second_present_progressive - grammar for matching multiword verb tense
g_verb_third_present_progressive - grammar for matching multiword verb tense
g_verb_plural_present_progressive - grammar for matching multiword verb tense
g_verb_first_past_progressive - grammar for matching multiword verb tense
g_verb_second_past_progressive - grammar for matching multiword verb tense
g_verb_third_past_progressive - grammar for matching multiword verb tense
g_verb_plural_past_progressive - grammar for matching multiword verb tense

w_verb_first_present_progressive - alias for corresponding grammar; a hack
w_verb_second_present_progressive - alias for corresponding grammar; a hack
w_verb_third_present_progressive - alias for corresponding grammar; a hack
w_verb_plural_present_progressive - alias for corresponding grammar; a hack
w_verb_first_past_progressive - alias for corresponding grammar; a hack
w_verb_second_past_progressive - alias for corresponding grammar; a hack
w_verb_third_past_progressive - alias for corresponding grammar; a hack
w_verb_plural_past_progressive - alias for corresponding grammar; a hack

The third directive of the WORDGROUP construct is _merge, which lets you copy words from another wordgroup. (Example below.)

Note that having multiple WORDGROUP definitions for the same variable name is perfectly valid. Subsequent definitions will be automatically merged with the existing wordgroup. For instance, below, the w_adjective wordgroup could very well have been defined previously in a header file, such that our definition merely adds a few words to it.

EXAMPLE:

WORDGROUP w_adjective
_merge w_color

The latter will copy all of the words in w_color into w_adjective. The words in w_color remain unchanged.

 

C. GRAMMARLIST <listname>

A GRAMMARLIST defines a list of grammars. As with the WORDGROUP, each grammar must appear on a separate line, and you use a blank line to terminate the list. Note that a grammar can contain virtually any symbolic name, including the names of other grammar lists. Below, two of the grammars contain a "g_prepositional_phrase", which is a GRAMMARLIST defined elsewhere.

For readability, the name of a GRAMMARLIST should be preceded by "g_".

EXAMPLE:
GRAMMARLIST g_verb_phrase
w_verb
w_adverb w_verb
w_verb g_prepositional_phrase
w_verb w_adverb g_prepositional_phrase

The above grammarlist will generate phrases like "walked", "quickly walked", "walked to the store"; etc.

Incidentally, grammarlists don't have to be purely symbolic; they can also contain straight English words.

EXAMPLE:
GRAMMARLIST g_exclamation
Oh my !
Gee !
Golly !

Like wordlists, grammarlists have a _merge directive.

EXAMPLE:

GRAMMARLIST g_phrase
_merge g_prepositional_phrase
_merge g_participial_phrase
_merge g_infinitive_phrase

The latter will create one big list of grammars that can generate (or match) a wide variety of phrases. The component lists are not changed when their contents are copied into g_phrase.

 

D. WRITE

Each story-generating program has one WRITE construct. The WRITE construct is the list of grammars to be "filled in" and displayed to the user every time the "Write a Story" button is clicked. Each S program should have at most one WRITE command.

If more than one grammar is defined, the engine will pick one of them to "fill in" each time the button is pressed. Typically, the right thing is to package all of the variability of a construct in its own grammar and use a WRITE command like this:

EXAMPLE:
WRITE
g_story

Alternately, if you want to do some linear processing during the construction of a story, you might use a patterngroup (duscussed below) and put a call to the patterngroup in the WRITE block:

EXAMPLE:
PATTERNGROUP p_story
_
_property i_story |e|
_property i_action |e|
_
_in _write
_eval `c_location {_add_person c_hero}
_eval `c_location {_add_person c_witness}
_eval `c_hero {_add_item c_hero_item}
_set i_story = |t| `c_hero->i_name w_meet_word `c_witness->i_name `c_location{_preposition} the `c_location->i_name . |n|
_set i_story = i_story p_converse {c_witness c_hero c_hero_item}
_set i_story = i_story `c_hero {_action c_hero_item c_witness} 
_out i_story

WRITE
p_story{_write}

 

E. CONST <varname> <args...>

The CONST identifier is somewhat of a misnomer. The variable it defines *will* remain constant for the duration of a given story, but its value will be recalculated every time a story is written. Essentially, the S interpreter stores whatever symbols follow a CONST declaration and "evals" them at the beginning of every WRITE operation.

EXAMPLE:
CONST c_color w_color

Every time a story is written, c_color will get evaluated to an English word stored in the w_color list. For the rest of the story-generation process, c_color will contain that word: "red", "yellow", "blue"; etc.

 

F. MAP

A MAP is a many-to-one mapping of words to words. It can be useful for creating simple lookup tables. Internally, a MAP is used to create the m_verb_form map, for instance, a map that matches a conjugated verb to its "root".

EXAMPLE:
MAP m_hero_gender
Bill male
John male
Angie female
Sue female

You could use the above map in a grammar like this:
Bill is a m_hero_gender { Bill } .
which would generate "Bill is a male." Note that the braces are optional with maps, because maps take only a single argument. Also, notice that you can pass unevaluated expressions to maps; they will be evaluated before the lookup occurs. Thus, the following stretch of code will generate four possible sentences that identify the gender of each possible hero:

WORDGROUP w_hero
Bill
John
Angie
Sue

MAP m_hero_gender
Bill male
John male
Angie female
Sue female

CONST c_hero w_hero

WRITE
c_hero is a m_hero_gender c_hero .

 

G. PATTERNGROUP

By far the most complex structure in the S language is the patterngroup. At their simplist, patterngroups are structured like typical ELIZA-style chatterbots. They take a grammar as an argument and attempt to "match" various substrings within the grammar. If a match is found, a response is chosen from a list of responses. This behavior is encoded as lists of "_in" and "_out" patterns. If any of a series of _in patterns matches, then one of the _out patterns will be returned randomly.

EXAMPLE:
PATTERNGROUP p_eliza
_in ~a hate ~b
_in ~a despise ~b
_in ~a loathe ~b
_out Are you normally a hateful person ?
_out I can help you deal with your anger .
_out Indeed !

Above, any sentence that contains the words "hate", "despise", or "loathe" will trigger one of the _out responses. The ~ character specifies a wildcard variable that will match any number of words. There are two kinds of word-level wildcards, ~ and $. You can precede any string with one of these characters to create logically named pieces of an input grammar, and you can use these pieces in _out lines. Some examples of this syntax are shown in the next section.

 

G.i PATTERNGROUP WILDCARDS

The two primary wildcards are:
~ - matches against any 0 or more words
$ - matches against any single word

Let's look at a more complicated example. This one uses two in-out "blocks" to catch two different expressions. (You can define as many blocks as you like.)
PATTERNGROUP p_eliza
_in ~a I hate the $a ~b
_out I hate the $a , too .
_in ~a I like the $a ~b
_out me ? I hate the $a .

To see how this works, look at what p_eliza responds to the following inputs:
"Hmmm , I hate the government ." ----> "I hate the government , too ."
"I like the sound of music ." ----> "me ? I hate the sound ."

The way you evoke a patterngroup in an S language expression is like this:
p_eliza { Hmmm , I hate the goverment . }

The above would evaluate to "I hate the government , too ."

The grammar passed to a patterngroup can be stored in a CONST variable or can be an expression that has yet to be evaluated. Before pattern-matching occurs, the argument to a pattern-match call is fully evaluated down to words. The following are all legal constructions:

CONST c_comment Hmmm , I hate the government .
CONST c_response p_eliza { c_comment }

GRAMMARGROUP g_story
Bob said , " c_comment " Then Eliza replied , " c_response "

WRITE
g_story

A separate class of wildcards can be used to pattern-match *within* a word. These wildcards are very similar to the wildcards used in DOS and are exactly the wildcards used in Visual Basic. Briefly,
* - match any 0 or more characters
? - match any one character
# - match any one digit
[abc] match a character that is "a" OR "b" OR "c"
[!abc] match a character that is NOT { "a" OR "b" OR "c" }

For additional details, see the Visual Basic documentation. Only * and ? are officially part of the S language, because the programmer will likely poop out during the Java port.

EXAMPLE:
PATTERNGROUP p_eliza
_in ~a you'* stupid ~b
_out No , I'm not !

p_eliza { I think you're stupid and ugly . } ----> No , I'm not !

You can also pattern-match against other symbolic names: the name of a word list, a grammar list, a CONST value; etc. The match will be returned in a variable named $<symbol>n, where n indicates the nth occurrence of the symbol in the input pattern. The following examples should make this more concrete:

EXAMPLE:
PATTERNGROUP p_eliza
_in ~a you're w_insult_adj ~b
_out No, you're the one who's $w_insult_adj1 !
_in ~a you're w_insult_adj and w_insult_adj ~b
_out I might be $w_insult_adj1 , but you're dreaming if you think I'm $w_insult_adj2 .

Notice that the second _in line matches against the w_insult_adj wordgroup twice and receives the matches in the system-generated variables $w_insult_adj1 and $w_insult_adj2.

You can also match against maps, grammars, and CONST values. The following is an example of a patterngroup that converts a sentence to past-perfect tense.

EXAMPLE:

PATTERNGROUP p_convert_to_past_perfect
_in ~a w_verb_participle ~b m_verb_form ~c
_out ~a $w_verb_participle1 ~b f_past_perfect $m_verb_form1 ~c
_in ~a m_verb_form ~b
_out ~a f_past_perfect $m_verb_form1 ~b

Clearly, this is an incomplete implementation, but you can see how the wildcards allow you to match against the system's m_verb_form map to receive a "mapped" value, in this case the root form of a matched verb. By matching against the system's w_verb_participle wordgroup in the first _in line, we correctly convert a sentence that begins with a participial phrase. The f_past_perfect symbol is a function that "casts" the root form of a verb to past perfect. Functions will be covered later.

For complex matching operations, you have the option of defining a list of patterns inside a grammargroup. In other words, you can create grammargroups that contain wildcards. When you match against such a grammargroup, the complete match will be returned in a single variable, $<grammargroup>n; i.e. your _out line will have no knowledge of the wildcards that matched at the deeper level.

EXAMPLE:

GRAMMARGROUP g_match_participial_phrase
w_verb_participle and w_verb_participle w_noun ,
w_verb_participle and w_verb_participle w_noun
w_verb_participle and w_verb_participle ,
w_verb_participle and w_verb_participle
w_verb_participle ~a w_noun ,
w_verb_participle ~a w_noun
w_verb_participle ,
// etc.

// revisiting the past-perfect converter...
PATTERNGROUP p_convert_to_past_perfect
_in g_match_participial_phrase ~a m_verb_form ~b
_out $g_match_participial_phrase1 ~a f_past_perfect $m_verb_form1 ~b
_in ~a m_verb_form ~b
_out ~a f_past_perfect $m_verb_form1 ~b

 

G.ii PATTERNGROUP FUNCTIONS

In addition to the _out lines, patterngroups provide numerous functions that can be used during an "_out block." They are listed briefly in the following sections:

 

_set <name> <value...>

Assigns a symbol to a value. <name> is the name of a CONST variable or patterngroup "property" (covered below). <value...> is one or more symbols comprising a grammar.

 

_createWordGroup <name>
_createGrammarGroup <name>
_createMap <name>

Respectively, these each create an empty wordgroup, grammargroup, or map named <name>.

 

_addWord <name> <word>

Adds a word to wordgroup <name>. To specify a word that contains spaces (i.e. "living room"), use underbars to denote the spaces.

EXAMPLE:

_addWord w_places living_room

 

_removeWord <name> <word>

Removes a word from wordgroup <name>. To specify a word that contains spaces (i.e. "living room"), use underbars to denote the spaces.

EXAMPLE:

_removeWord w_places living_room


_addGrammar <name> <grammar...>

Adds a grammar to grammarlist <name>. <grammar...> can be one or more symbols separated by spaces.


_addMap <name> <key> <value>

Adds a key-value pair to map <name>.


_removeMap <name> <key> <value>

Removes a key-value pair to map <name>.


_appendPattern <listname> <pattern...>

Appends a line of code to an existing patterngroup.

EXAMPLE:

_appendPattern p_pattern _in ~a golly ~b


_prependPattern <listname> <pattern...>

Prepends a line of code to an existing patterngroup.

EXAMPLE:

_prependPattern p_pattern _in ~a golly ~b


_if <grammar1...> < =, ~=, =~, ^= > <grammar2...>
_else
_endif

These work almost like you would expect. <grammar1...> and <grammar2...> can be any list of symbols. Before the comparison takes place, both grammars are evaluated down to words. Four comparison operators are available:

= - <grammar1...> EQUALS <grammar2...>
~= <grammar1...> is an ORDERED SUBSET of <grammar2...>
=~ <grammar2...> is an ORDERED SUBSET of <grammar1...>
^= <grammar1...> DOES NOT EQUAL <grammar2..>

The one behavior that may not be intuitive is that these conditionals are context-sensitive based on whether they appear in an _in block or an _out block. The S parser religiously demands that an _in line be satisfied before it will execute commands in an _out block. The corollary is that you cannot substitute an _if for an _in -- you can only branch between _in lines or branch between groups of function calls during an _out block. The following examples illustrate what this means in practice.

EXAMPLE (wrong!)

_if c_hero = Bob
_set c_temp male // command will be skipped because no _in has been satisfied !!!
_else
_set c_temp female
_endif
_in Sally
_out I like Sally
_in Suzie
_out I like Suzie

EXAMPLE (correct)

_if c_hero = Bob
_in ~a // a kludge and a trick, but this provides the effect we want.
_set c_temp male
_else
_in ~a
_set c_temp female
_endif
_in Sally
_out I like Sally
_in Suzie
_out I like Suzie

Note that pattern-matching terminates only when an _out line is reached. It's entirely possible, as the above example illustrates, to match an _in line and not terminate "execution" of the patterngroup.

EXAMPLE (correct)

_in Sally
_out I like Sally
_
_in Bob
_if c_hero = Bob // We are inside an _out block, so we can branch between commands without worrying about matching an _in.
_set c_temp like
_else
_set c_temp hate
_endif
_out I c_temp Bob
_
_in Suzie
_out I like Suzie

Notice that we can make use of a _ on a line by itself to make the code more readable.


_eval <grammar...>

This command forces the S Language to evaluate a grammar, a useful tool for forcing the parser to do things in a certain order.

EXAMPLES:

_eval c_hero
_eval p_monster {_add_item sword} // add a sword to the monster's inventory


FUTURE WORK: There are currently no commands for creating a patterngroup. This will be added later if a need arises. Preferably, there would be one constructor function that applied to any object.

 

G.iii PATTERNGROUP INHERITANCE

(This section presumes that the reader is already familiar with object-oriented programming.)

Yes, patterngroups can be used to define classes. In a crude way, you can use wildcards and _in lines to define "overloaded" member functions. With the use of the _parent and _child keywords, you can effectively make these functions virtual. The _property keyword allows you to define properties that can either be inherited or overridden by subclasses.

The one strange thing about S Language patterngroups is that, while parent properties are instanced for each derived patterngroup, the lowest level patterngroup is NOT instanced -- or, rather, it IS the instance of the parent. If you want multiple instances of a child patterngroup, you need to create multiple children of that patterngroup. Concevably, a programmer might create several empty child patterngroups that would serve as instances of a single patterngroup that contains all of the code.

The following code shows how to create a superclass of all "persons" and then a subclass that defines a particular person.

EXAMPLE:

// superclass of all people
PATTERNGROUP p_person
_
_property i_name DefaultName
_property i_sex male
_property i_pronoun he
_property i_possessive his
_property i_reflexive himself
_
_in SayHi
_out Howdy , pardner .
_in ~a I hate i_name ~b // you can pattern-match against a property value
_out I'm i_name , and I think you're a jerk . // you can also use property names in _out lines

// subclass defining Sally
PATTERNGROUP p_sally EXTENDS p_person // Use "EXTENDS" to specify inheritance
_
_property i_name Sally // these property definitions automatically override the superclass's
_property i_sex female
_property i_pronoun she
_property i_possessive her
_property i_reflexive herself
_
_in SayHi // override superclass's method
_out Hello .
_in SayHiCampy
_out _parent SayHi // call superclass's method

The previous code should show off the bulk of the syntax. In addition, a superclass can be assured that a method-call will begin at the LOWEST subclass by using the _child keyword in an analagous way to the _parent keyword above.

Above, you might want to add a method to p_person that knows how to construct a complex greeting from "SayHi" and "AskState", where those two methods can be overridden by subclasses. You could do that with this method:

EXAMPLE:

_in ComplexGreeting
_out { _child SayHi } { _child AskState }

 

H. REFERENCES

With patterngroups, one can begin to generate something that resembles procedural code. Evaluation of symbols still proceeds in a somewhat rigid fashion, however. Very often, the programmer will want to store the names of symbols and evaluate them at a specific time down the road.

A possible story-generation approach, for instance, would be to implement grammar and semantics as separate entities that communicate via references. There might be an entity that knows how to write a descriptive sentence but knows nothing about any particular topic. That entity could receive a reference to a second entity that knows all about "mountains," and the sum of the two might produce a descriptive sentence about mountains. This sort of thing can be implemented with patterngroups and patterngroup references.

The syntax for references is straightforward. You create a reference by preceding a symbol with "@". To reference the symbol, you "strip off" the @ with the backward single-quote character, "`".

EXAMPLE:

He was a @w_noun .

would generate:

He was a @w_noun.

whereas:

He was a ` @w_noun .

might generate:

He was a clown.

Before ` is applied, the S interpreter evals the subsequent symbol. This means that you can store references in CONST variables, wordlists, properties, and so on.

EXAMPLE:

CONST c_ref @w_color

WRITE
`c_ref.

might generate:

red.

A more useful example is the patterngroup architecture described above. The following code divides "grammar" and "knowledge" into separate patterngroups. The grammar entity takes a reference to a knowledge entity. As long as an entity implements the interface expected by the grammar entity, it can be "expressed" in words by the grammar.

EXAMPLE:

// We want to implement patterngroups that will allow
// a syntax in which we pass a few arguments to a knowledge-base,
// which then selects appropriate adjectives, nouns; etc.
// p_emotion_description is the knowledge-base. It takes the name
// of the hero and a "reaction" ("concern", "indifference"; etc.)
// as arguments. Notice that only a *reference* to p_emotion_description
// is passed to p_noun_quality_description. The latter strips off the
// "@", calls p_emotion_description with the original arguments, and adds
// a third argument that serves as a request for a part-of-speech.
// This relationship should be made clearer from the following code listings
// of each of the entities involved.

// First of all, this is how we use the entities at the high level...

GRAMMARGROUP g_hero_emotion_sentence
f_cap p_noun_quality_description {@p_emotion_description c_hero c_hero_reaction}.

// This is the abstract grammar. We use _set to set a local property to the
// name of the referenced patterngroup.

PATTERNGROUP p_noun_quality_description
_property i_p patterngroup-name
_in $p ~a
_set i_p `$p
_out i_p { ~a subject } i_p { ~a linkingverb } i_p { ~a subject_adjective }
_out i_p { ~a subject } i_p { ~a linkingverb } i_p { ~a subject_adjective } and i_p { ~a subject_adjective }
_out , i_p { ~a subject_phrase } , i_p { ~a subject } i_p { ~a linkingverb } i_p { ~a subject_adjective }
_out i_p { ~a subject } i_p { ~a linkingverb } i_p { ~a subject_adjective } , i_p { ~a subject_phrase } ,

// Here is a crude "knowledge-base" that will interface with the above grammar.
// The "c_hero" and "c_reaction" arguments show up here as $cast and $emotion.
// The names were changed just in case you were on the brink of falling asleep.
// Obviously, this patterngroup references yet other patterngroups. We don't
// list all of the code for every reference.

PATTERNGROUP p_emotion_description
_in $cast $emotion subject
_out $cast // the subject of the sentence is the name of the hero, c_hero.
_in $cast $emotion pronoun
_out p_cast_pronoun $cast
_in $cast $emotion linkingverb
_out was
_in $cast $emotion subject_adjective
_out p_emotion_adj $emotion
_in $cast sorrow subject_phrase
_out staring out the window all day long
_out wishing it would rain and wash away the hurt
_in $cast concern subject_phrase
_out fearing the worst
_out afraid of the consequences
_in $cast bemusement subject_phrase
_out laughing to m_cast_reflexive $cast
_out anxious to tell m_cast_possessive $cast friends
_in $cast jealousy subject_phrase
_out seething
_out suspicious of everyone
_out fed up with living

 

I. BUILT-IN FUNCTIONS

Overlaying the basic programming constructs are a few system-defined "functions" that allow you to do some special-case processing. Here's a list of those functions and a brief description of each.


f_cap

Capitalizes the first letter of the next word. NOTE: you don't need to use f_cap for the first word of a sentence because the WRITE command does that automatically. Regretably, an "..." probably triggers a capital when it shouldn't, but that will be fixed in a later version.

EXAMPLE:

WRITE
this is a nice sentence .

generates: "This is a nice sentence."

WRITE
this is a f_cap nice sentence .

generates: "This is a Nice sentence."

Note that what we're calling "functions" behave more like simple tags. For instance, you can't capitalize every word in a phrase with the following syntax:

EXAMPLE: (wrong)
f_cap { only the first word of this sentence will be capitalized . }

Perhaps this will change in a future version of the S language.


f_start_quote, f_end_quote

These cumbersome constructs have been replaced with the strings |qs| and |qe|, respectively. They let you distinguish (for the purposes of spacing) between starting and ending quotation marks.

EXAMPLE:

either of these: 
John said , f_start_quote Hi there . f_end_quote
John said , |qs| Hi there . |qe|

generate:

John said, "Hi there."


f_paragraph

Inserts two carriage returns, which effectively create a break between two paragraphs. (This can optionally be accomplished by using two new-line tags, which look like this: |n|.)


f_plural <noun>

Converts a singular noun to plural.

EXAMPLE:

The f_plural dog .
generates:
The dogs.


f_first_present <verbroot> // first-person...
f_first_past <verbroot>
f_second_present <verbroot> // second-person...
f_second_past <verbroot>
f_third_present <verbroot> // etc....
f_third_past <verbroot>
f_plural_present <verbroot>
f_plural_past <verbroot>
f_infinitive <verbroot>
f_past_perfect <verbroot>
f_past_subjunctive <verbroot>
f_participle <verbroot>
f_first_present_progressive <verbroot>
f_second_present_progressive <verbroot>
f_third_present_progressive <verbroot>
f_plural_present_progressive <verbroot>
f_first_past_progressive <verbroot>
f_second_past_progressive <verbroot>
f_third_past_progressive <verbroot>
f_plural_past_progressive <verbroot>
f_future_progressive <verbroot>
f_future <verbroot>

All of the above take the "root" of a verb and convert it into a particular tense. The "root" of a verb is defined inside a wordgroup with the _verb keyword. Roots can also be obtained with the following S language structures: the name of a wordgroup that contains _verb definitions, a pattern-match against such a wordgroup, and a pattern-match against system-defined maps such as m_verb_form, m_linking_verb_form; etc.

EXAMPLE:

WORDGROUP w_verb
_verb INTRANSITIVE \
to_run run \
run ran \
run ran \
runs ran \
run ran \
run running

GRAMMARGROUP g_tenses
The cow w_verb to the barn . The cow f_present w_verb to the barn . The cow f_past run to the barn .

WRITE
g_tenses

The above code generates:
The cow run to the barn. The cow runs to the barn. The cow ran to the barn.

 

J. ADDITIONAL COMMANDS

 The S parser also supports a few global-level directives, the most useful of which is INCLUDE, which tells the parser to parse the contents of a specified file before continuing with the current one.

EXAMPLE:

INCLUDE StandardEnglish_LIB.s

Notice that the filename should not be enclosed in quotes. The filename must be relative to the topmost file in the INCLUDE tree. Explicit filenames will be parsed incorrectly.

Other global commands include:
ADDWORD <groupname> <word> // adds a word to a predefined wordgroup
ADDGRAMMAR <groupname> <grammar...> // adds a grammar to a predefined grammargroup
ADDMAP <groupname> <key> <value> // adds a map to a predefined mapgroup
PREPENDPATTERN <groupname> <grammar...> // prepends a line to an existing patterngroup
APPENDPATTERN <groupname> <grammar...> // appends a line to an existing patterngroup

Some thought needs to be given to whether these should continue to exist, as a meta-language in the WRITE block, or whether all function-like mumbo-jumbo should stay inside patterngroups.

 

K. SPECIAL CHARACTERS

These strings can be used to specify the corresponding special characters:

|n| - newline
|qs| -- start-quote
|qe| -- end-quote
|t| -- tab

 

L. FUTURE WORK

 Aside from generating some data to see whether the S Language actually has any utility as a story-generator, I am looking at doing the following tasks next.


_save <object> <filename>
_saveall <filename>

Some serialization would be nice, especially something Web-friendly so that S Language chatterbots could talk to people around the world and learn from them.


MARKOVGROUP <name> <corpus> <params...>

Wouldn't it be cool to build a Markov model from a textual corpus and use it as any other S Language construct?


_foreach <var> in <group>

A for-each statement would be nice, but I cringe to continue sliding into a procedural model of programming when the S Language was envisioned more as a randomized solver of complex layered definitions.


Speed. This parser, based mostly on string-comparisons, would slow to a crawl if faced with a modestly large dataset. I would have benefitted immensely from a compiler course during college.


A Java port. I would have written this in Java to begin with, but the core of the program was written as a Visual Basic tutorial for my young cousin and I never had the gumption to switch over.

 

Also... as mentioned at the top, I have sketched out a new design for the language which unifies the language constructs into a smaller, more versatile set.  Someday I will get a few months of free time and be able do a complete refresh... 


M. CONCLUSION

That concludes the tour of S language constructs.  If you find any bugs or have interesting projects to share, please use the contact page on this web site.