Site Tools


epic_scripting

An introduction to scripting with EPIC

The intended audience for this guide is users of EPIC5 who have some programming experience and want to understand the scripting language that is used to customize EPIC. This guide only begins the learning process and does not cover all of the important details.

The ircII language

EPIC is a fork of ircII. EPIC uses the same fundamental scripting 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 that 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.

epic_scripting.txt · Last modified: 2013/09/11 14:17 by 127.0.0.1