An introduction to scripting with EPIC
DISCLAIMER:
This document is currently being written. It is incomplete and probably
still has errors and important omissions. Please be patient. Thanks.
Non-Technical stuff to help you make sense of this
Your host:
Jeremy Nelson, the maintainer of EPIC, and perhaps others who have
made changes but chose to remain anonymous.
Intended audience:
Users of EPIC5-0.2.0 or later who have some programming experience and want
to understand the language that epic uses to customize itself. This guide
only begins the learning process and does not cover all of the important
details.
EPIC uses the ircII language:
EPIC is a fork of ircII. EPIC uses the same fundamental language as
ircII, which we call the "ircII language" for lack of a better term.
All ircII-derived clients (including BitchX, ScrollZ, and CToolZ) use
the ircII language. The primary differences between the clients are in
the commands, functions, and variables they offer.
IrcII is like, and unlike, anything else:
IrcII has things in common with perl, tcl, ruby, and php, but it is more
unlike them than like them. The ircII language is inanely simple -- there
are very few rules, and the rules that are there are strictly enforced,
and where there are no rules, all bets are off. This is often a stumbling
block for the new user.
Rule #1 -- Everything is a string and strings are
everything:
In ircII, everything is a Plain Old C String. All data, and all code, are
stored as strings, and there is little distinction made between them. You
are free to treat your data as code and your code as data. Because of this
fluidity, ircII does not have byte compiling or flow control in the normal
sense.
Rule #2 -- Whitespace is important (usually):
In most languages, whitespace is not important. You can run letters
and punctuation together with reckless abandon. But in ircII, whitespace
is used to separate things in a list. Most nearly everything in ircII is
a list. Spaces are always treated as normal characters: you would not
expect a language to ignore 'e's in your code whenever it wanted to, and
ircII does not ignore spaces. Each character (even spaces) means something.
If your editor replaces a tab with multiple spaces, take care!
Writing code
The syntax of the ircII language is suprisingly simple.
- Block -- A block is a set of semicolon-separated statements.
- Statement -- A statement is a single operation in the language.
Statements are either Command Statements, Block Statements, or
Expression Statements.
- Expression Statement -- A statement that begins with the @ character,
or a statement that is surrounded entirely by parenthesis, is an Expression
Statement.
- Block Statement -- A statement that is surrounded entirely by
curly braces is a block statement. The block statement contains a block,
naturally.
- Command Statement -- Any statement that is not an expression
statement and not a block statement is by rule a command statement. A command
statement is a list of space-separated words, where the first word is the name
of a command and the rest of the words are the arguments to that command.
An Expression is a thing that has operands and operators, where the
operators consume one or more operands and replace them with a new value.
A expression is a thing like "3 + 4" or "var = 7". Expressions are explained
in much more detail below.
A Command is an action to be performed. A command can either be
built-in to the client (such as JOIN or QUIT) or it can be a string that you
have registered as an alias. Everything that is not an expression is a
command.
That's it, really! Unlike most normal programming languages, ircII has no
builtin block statements or flow control. Whereas other languages have
native flow control built in, these things are Command Statements
in ircII.
What you can do with your code
The ircII programming model is event driven -- your script does not control
EPIC, but rather you tell EPIC what things you are interested in. Whenever
your event handlers are not being run, EPIC is off busy doing all the other
things irc clients do.
So if you don't register any event handlers, EPIC is happy to be a very
simple irc client that behaves very much like ircII. Unfortunately, ircII
is very minimal by today's standards and most people want to change things.
Events
One of the first things people want to change is how channel joins look.
You know the default appearance...
*** Henry (hcutter@example.com) has joined channel #irchelp
But let's say you want to put the time in there, and you want to change
the order of things. Here's an example that we'll walk through:
on ^join "* *" (nick, chan, userhost) {
xecho -b [$chan] JOIN: $nick \($userhost\)
}
If you skip ahead and read the intermediate technical stuff below, you
will already understand a lot of what you see here.
You register event handlers by using the ON command, and it takes
three arguments. The first argument is the type of event you are interested
in. The second argument is a wildcard pattern that you want to filter upon.
The third argument is the list of local variables to put the argument list
into. The fourth argument is a block of ircII code.
When something interesting happens, we say that an "event is thrown". The
client has a hardcoded list of events it knows how to throw. Each time an
event is thrown, an argument list is created that tells you the details of
why that event is being thrown. Each type of event has its own argument
list layout.
In this case we are hooking the JOIN event, which is thrown any time
someone (including yourself) joins a channel you are in. The argument list
for JOIN is:
- Nickname -- Who joined the channel
- Channel -- What channel was joined
- Userhost -- The user@host of the person who joined
We use an argument list to automatically assign these values to local
variables. Lots of older code doesn't use argument lists, and if you don't,
these values are available as numeric expandos ($0 == nickname,
$1 == channel, $2 == userhost). Both ways are fully supported and it doesn't
matter which one you use. This guide uses argument lists because it is
less confusing for the new learner.
You'll notice that there is a caret before the JOIN. This is the "noise"
level of the ON, and for anything you're doing in a script, you want it to
be the caret. The caret tells EPIC to perform this action instead of whatever
action it would have done by default. If you leave the caret out, EPIC will
do BOTH your handler and the default action.
The wildcard pattern in this case is the star, which means we want to match
everything. The wildcard pattern is a double-quoted word. Normally arguments
cannot contain spaces, but sometimes you must (such as here), so you can
surround words with spaces in double quotes and ircII will figure out you
mean one argument that has spaces in it. You can't just use double quoted
words anywhere you want (alas) but only where it is expected.
You use the wildcard pattern to filter out events that you are not interested
in. The wildcard pattern is matched against the argument list, and if and
only if the pattern matches the argument, does your code get run. This
allows you to set up overlapping ons that act as exceptions:
on ^join "* *" (nick, chan, userhost) {
xecho -b [$chan] JOIN: $nick \($userhost\)
}
on ^join "bob *" (nick, chan, userhost) {
xecho -b HUZZAH! HUZZAH! HUZZAH! $nick!$userhost has joined $chan!
}
So when Bob joins the channel, the second event handler is run, and when
anyone else joins, the first one is run. How does ircII know which on to
use? Each handler gets scored based on how many non-wildcard characters it
has in it. The first handler has a score of 1 (the space), and the second
handler has a score of 4 ("bob" and the space). If more than one event filter
matches the argument list, the one with the highest score "wins".
Next after the wildcard pattern is the argument list. As I said before,
the argument list is optional. If you use the argument list, then the
values of the event's argument list will be shifted off into the named
local variables. If you don't use the argument list, then the values of
the event's argument list go into $* and are available using the numeric
expandos ($0, $1, $2, etc).
Finally comes a block of code you want to have run. The block of code is
surrounded by curly braces (this is a requirement of the ON command and is
not optional). Each time the event is thrown, the filter matches the
argument list, and this event wins the tie-breaker, then this code is run.
You can put in anything here that you want epic to do. We will output a
more interesting message to the screen than epic would have.
Aliases
Aliases are commands that you create yourself. As I said before, an alias
is nothing more and nothing less than a Plain Old String (POS) that you say
contains a block of ircII code. When you create an alias, it is inserted
into the list of valid commands, and it can be used by he user at the input
prompt, and it can be used by your script in any command statement.
But what it doesn't do is try to figure out what is in your code. This is
not done until you use the alias. If you put garbage in an alias, and never
used the alias, ircII would never know and never care. You can change your
code on the fly at runtime, and your changes will take effect immediately.
If you put garbage in an alias, and use it a million times in a loop, ircII
would notice every time there was an error and tell you. It must do this
every time, because it doesn't know that you didn't rewrite the code from the
last time.
Specifically, an alias is a Plain Old String that contains ircII code that
is assigned a name that can be used in a command statement. You can create
an alias like this:
alias MyNewAlias {
echo Eek! You scared me!
}
This adds a new command "MyNewAlias" that you can use in a command statement.
To test it out, run /MyNewAlias at the input prompt. If the alias was
created correctly, you would see the output in your window.
Aliases, like On event handlers, can receive arguments. If you provide an
argument list, the arguments are put into the variables in the list. If you
do not provide an argument list, the arguments are put into $* and are
available as numeric expandos ($0, $1, $2).
Variables
Variables (also called Assigns) are macro values you create yourself. All
variables are strings, and there are no other types available. IrcII stores
variables as C strings, so they cannot contain nuls (ascii 0). This means
that variables are not suitable for storing binary data.
Variables can either be Global Variables or Local Variables.
A Global Variable can be seen in any place, and never goes away or loses
its value. But you can only have one value per global variable name, and
this is a hassle for people who need temporary values or are writing
recursive aliases. Local variables allow you to keep variables that won't
conflict with anybody else, and get automatically deleted when you're done
with them. Remember argument lists? Those use local variables, so those
variables can only be seen while the alias or on is running, and get
automatically deleted when the alias or on is finished.
Intermediate, Technical stuff
Language syntax:
The ircII language's syntax is suprisingly simple.
- Block -- A block is a set of semicolon-separated statements,
optionally surrounded by curly braces.
- Statement -- A statement is a single operation in the language.
Statements are either Command Statements, or Expression Statements.
- Expression Statement -- A statement that begins with the @ character,
or a statement that is surrounded entirely by parenthesis, is an Expression
Statement.
- Expression -- A thing like "3 + 4" or "var = 7" where there are
operands and operators, and operators reduce operands by performing operations
until there is only one operand left. This is the value of the expression.
An expression is considered "false" if the value is either the number 0 or
a zero-length string. Anything else whatsoever is considered true.
- Block Statement -- A statement that is surrounded by curly braces
is a block statement. The block statement contains a block, naturally.
- Command Statement -- Any statement that is not an expression
statement and not a block statement is by rule a command statement. Each
command statement is made up of a command, and optionally a single space
and an argument list. The command is executed with the argument list, and
it does not return a value.
- Command -- A command is some action to be performed, such as JOIN
or QUIT
That's it, really! Unlike most normal programming languages, ircII has no
builtin block statements or flow control. Whereas other languages have
native flow control built in, these things are Command Statements
in ircII.
Basic Flow Control:
It's assumed if you're reading this document you already know about the
command statements that control things on irc (like JOIN and SERVER).
But you probably are not familiar with how to do things like if's and while's.
The basic flow control commands in ircII are:
if (expression) {
block
}
If the value of the expression (described generally above, and
specifically below) is true, execute the block of code.
Wait -- here is an important aside -- it is important to understand that
IF is a command, and it must follow the rules of the language syntax.
In other languages, whitespace is not important, but in ircII whitespace
is always very important. The above command statement begins with the
command (if) a space, and then the argument list. Although there are
conventions that are similar across several commands, each command is
independant and its argument list can be whatever it wants it to be.
The IF command takes an argument list that must contain an expression
surrounded in parenthesis (they are not optional) and a block of code
surrounded in curly braces (they are not optional)
if (expression) {
block
} else {
block
}
If the value of the expression is true, execute the first block of
code, and if it is false, execute the second block of code.
if (expression) {
block
} elsif (expression) {
block
} else {
block
}
If the value of the first expression is true, execute the first block
of code, and if it is false, but the value of the second expression
is true, execute the second block of code, and if they are both false, execute
the third block of code.
while (expression) {
block
}
Execute the block of code forever, as long as the expression is true.
If expression is false to begin with, then the block is not executed.
You may run the BREAK command to terminate the loop before the expression is
false. You may run the CONTINUE command to skip the rest of the statements
in the block and immediately re-test the expression.
fe (words list) variable list {
block
}
Loop over the Space Separated List of Words, putting them into local
variables and then executing the block of code. The list of words must
be surrounded by parenthesis. The variable list can contain 1 to 254
space separated variable names. The block of code must be surrounded by
curly braces. The variables you provide are regular local variables, so
they will clobber any local variables by the same name, and they will retain
their final value when the command is finished. You may use the BREAK Command
to terminate the loop before the word list is exhausted. You may use the
CONTINUE command to skip the rest of the statements in the block and
immediately put new values from the list into the variables.
Wildcard patterns:
About scripts:
When the EPIC client starts up, it reads a special startup script (usually
~/.epicrc) where it expects to find ircII code that replace the default
behaviors with special handlers which you have created. This special file
is called a startup script (also, "your ircrc"), and it is the only
opportunity you have to tell EPIC what you want it to do. Your startup
script can load other ircII script files, create aliases, register event
handlers, set default values, and so on. When your script is done running,
control is returned to EPIC and from that moment on, control is only returned
to your code whenever an event handler you registered is activated.