File formats11
Specifying Directories11
Language Mapping switches11
General switches11
Filename filtering12
Output file format12
Language specifications"13
Language element feature selection13
Languages13
77
77
88
99
00
The CRUNCH Language111144
The crunch compiler14
Data types15
Variables - types, scoping, argument passing16
Scoping17
Argument Passing18
Variable argument lists20
Returning values and parameters20
Returning values20
Pass by reference20
The int data type21
The float/double data type21
The string datatype21
The list (array) datatype22
44
Page 2
List assignment23
Making Lists23
Manipulating List Items24
Sorting Lists24
Searching Lists24
Informational Lists24
The declare datatype25
Structures25
Language Grammar25
Declarations26
Function definitions27
Loading a macro: main, _init27
Expressions27
Loop constructs: for, while, do28
Testing expressions: if29
Selection: switch29
Debugging macros333300
Primitives for showing output30
Macro tracing31
Debug applied to a function32
Debug on startup32
Debug buffering33
The vars() macro33
Buffers, Files and Windows333333
Buffer Attributes34
Ansi Mode buffer attribute34
The Backup Flag34
The Binary Flag34
Buffer Contents35
00
33
Buffer name, buffer ID and File name35
The Carriage-Return flag35
Page 3
Current cursor position35
Modified Flag35
Permissions & Read-only Flag35
Process Buffers36
Region Markers36
Symbol Table36
System Buffers36
Tab Settings36
Undo Information36
Character Maps37
Objects supported by CRiSP333388
File Types -- Text files and Binary Files39
Backing up Files39
Autosaving39
Core dumping40
Regions and markers40
Macros41
Global and Static macros41
Modules and static macros42
Registered Macros42
Timer functions45
Color Support46
Searching for text -- Regular Expressions444477
Character Escaping48
The wild card operators: ? and *48
Character Class: [..] and ..49
88
77
Matching Line boundaries49
Repetition: @ and +49
Regular Expression Grouping: ..50
Minimal and Maximal Matching50
Matching Direction51
Regular Expression Syntax Mode51
Page 4
GUI Based Objects555511
Terminology52
Windowing Systems Programming52
Overview of the Dialog box system53
Overview of an Object54
Creating a Dialog Box55
Object Types56
Geometry Layout59
Resizing dialog boxes, and Constraint management60
Object hierarchies: menu bars, tool bars, status panels62
Object hierarchies and grouping63
Sub-groups64
Groups and the DBOX_CONTAINER object65
Properties (private symbols)65
Dialog boxes and Callbacks66
11
Platform Specific Issues67
How to create your own Colorization file666677
Keyword Builder68
Character classes69
Global attributes69
Keywords69
Creating a new colorizer69
Dialog box buttons69
.KWD Keyword File Format70
Comments70
Section Name70
The flags= Directive71
Character Class Directives72
Keyword Directives74
Keyword Flags75
Keyword Regular Expressions76
77
Limitations of Colorization76
Case study #1: C colorizer77
This guide discusses how to write macros in the CRiSP extension language, known as CRUNCH. The
CRiSP software package is a highly flexible file editor for use by people who need to edit files, whether they
be programmers, or engineers. CRiSP is designed to be easy to use and have a familiar user interface.
Underlying all this power is a powerful macro language which implements the things you use and see in the
user interface.
You may want to write your own personal macros for CRiSP in order to make private tweaks to keyboard
bindings, or to write your own subsystems to do more complex actions. The CRiSP binary itself is a program
interpreter, interpreting the steps in the macros in order to perform some well defined action. As an
interpreter it has a lot of power and allows such things as keyboard mappings to be set up, editing of files,
creating dialog boxes, configuring colors, etc. Just about everything you see within the CRiSP user interface
is built up from macros, and there is therefore a lot of expressive power in the software.
This programming guide covers the following areas:
→Getting started with macro programming (pg. 7).
→General introduction to macro programming(pg. 8).
→CRUNCH compiler,.(pg. 14).
→CRUNCH language,.(pg. 14).
→Macro debugging(pg. 30).
→High-level data objects (e.g. windows, buffers)(pg. 38).
→How to create custom colorization languages(pg. 67).
→Interprocess communication(pg. 79).
→Description of the Dialog Box subsystem.(pg. 51).
→Macro Primitives Guide
Disclaimer
This manual and the CRiSP Macro Primitives manual describe how to use the facilities in CRiSP for
extending its facilities. Foxtrot Systems Ltd makes no guarantee on the validity of the information contained
herein. In addition, any errors in the macro language (compiler or CRiSP) may or may not be fixed at a later
date. Although the facilities described have been extensively tested, the language and tools have been
designed to provide an efficient and portable user interface to the editor. Any private use of these facilities
which lead to core dumps, crashing of machines, or loss of files will not be construed as a deficiency in the
software supplied.
Where possible, errors leading to core dumps or machine crashes will be fixed if a suitable bug report form
(see User Guide for a copy) is enclosed. Due to the nature of programming it is not possible to guarantee
that every combination of primitives will lead to expected behaviour.
Getting started with Macro programming
This section is designed to introduce the basic things you need to know to write your own macro. If you
require further information, consult the introduction at the top of the Programmers Guide on-line help.
If you have never programmed a macro in CRiSP before then here are some tips to get you started:
The macro language (known as crunch) is an ANSI-C like macro language, e.g. you define functions which
are callable (from the Command: prompt, for example), and which can be assigned to keys. The macro
language provides numerous data types and internal builtin functions. The most common data types are
strings and integers. (crunch does not provide support for pointers, but you will find you do not need these
anyhow).
Macro files end in the file extension .cr. To create a macro file, edit a new file with the appropriate extension.
For example, create a macro file called mymacro.cr. Insert the following into the buffer:
Page 7
# include<crisp.h>
void
mymacro()
{
message("This is my macro!");
}
The #include statement is something you will need sooner or later in the macros you write. Although it is not
needed for this simple macro, you may need it as you extend your macro. The crisp.h file contains
numerous constant definitions which you need for some of the builtin functions of CRiSP.
This macro file contains a single macro, called mymacro. You can execute this macro after you have
compiled and loaded the macro. On most machines, just press the key <Alt-F10> and the macro file will be
compiled and loaded into memory.
On some Unix platforms running the Motif window manager, you may find that <Alt-F10> zooms the CRiSP
editing window to the full screen size. In this case, execute the command load at the Command: prompt.
You can access the Command: prompt by pressing the <F10> key.
After compilation, you will find a file called mymacro.cm in the same directory as the original source. This is
the file which is important to CRiSP. (On the other hand, the file mymacro.cr is important to you).
You can add multiple macros to your macro file and build up complex personal macros to do whatever you
want. It is a good idea if you are going to do extensive macro writing to browse the CRiSP supplied macros
and investigate whether some function you want to write is not already available directly, or callable as a
subroutine.
If you want to have your macro file loaded automatically on startup then enable the
Options→Startup→Startup macro menu option. Type in the full path to your .cm or .cr file.
An Overview of the Programming Facilities
CRiSP is an interpretive language execution engine, combined with support for very high level data objects.
The types of objects CRiSP knows how to manipulate is much more than for a standard programming
language. CRiSP supports basic primitive data types, such as integers, floating point numbers and strings,
as well as high level data objects such as buffers, dialog boxes, keyboard mappings, etc.
The function of CRiSP as a file editor is the combination of the execution engine (the CRiSP binary) and the
various macros supplied as part of the distribution. The supplied macros provide a wide variety of user
interface functions which can be tailored, by virtue of the various set up options, or reprogrammed by
changing the macro sources.
The macros are supplied in a source form, which is a language loosely based on the ANSI-C language, and
a compiled format. The source language provides various programming facilities and is designed so that
macro programmers can write and document maintainable macros. Macros can be very sophisticated and
involved, so macro programmers should create and support macros with respect. It is possible to write
powerful one-liner macros, but if you are going to be writing a lot of macros, then you will need to organise
things so that you can review and update the macros at a later date. This is no different from programming
in any ordinary language.
The compiled format is designed to be loaded into CRiSP much faster than raw interpretation of the macro
source can be. But this does mean that you have to actually compile the macro sources before CRiSP can
execute them. Fortunately this is very easy and CRiSP provides various facilities to help in this.
The underlying machine-code of CRiSP is a Lisp-like language. The CRUNCH language is compiled into an
internal form of the lisp language. The Lisp-like language has no official name, but files written using this
syntax normally have a '.m' file extension. Crunch language files have a .cr file extension. Compiled macros
have a .cm extension.
The lisp language may be considered the assembly language of CRiSP. It is exceptionally rare to code
directly in the lisp language since the CRUNCH language provides a superset of functionality (such as
consistency checking, common expression elimination, and other optimisations), plus the code is much
more maintainable.
See also
→Getting started with macro programming (pg. 7).
Page 8
→Writing your own macros(pg. 9).
→The .m language(pg. 90).
→The crunch language(pg. 14).
Writing your own macros
Private macros can be loosely categorized into two types: small one-off macros which are used to make
some piece of functionality easier to use (e.g. mapping certain editing commands to special keystrokes), or
major projects in their own right. The entirety of the CRiSP product can be broken down into major
subsystems (Unix mail, the setup dialog boxes) or simple value-added services, such as capitalizing words,
repeating the last search.
The bits that go to make up CRiSP as a whole are structured in a way that has allowed CRiSP to evolve and
allow room for manoeuvre as future functionality is added. CRiSP and the macro environment is a bit like the
Microsoft Windows 3.x environment in that all the macros are co-operative. Most macros are independent of
one another but within the context of a file editor, the macros are all adding value to the software.
When you come to write your own macros, you will need to think and understand what you are trying to
achieve. For one-liner macros, there is little to think about and you can pretty much achieve what you want
once you understand the mechanics of CRiSP. If you are attempting to build a complex macro then it can be
useful to step back and think about what you want to achieve. Much of this is common sense and applies to
software development in general, but it is worth understanding the environment you are going to be
programming in. Much of this manual is devoted to explaining the technical concepts and issues in writing
your macros. This section is more concerned with taking a steady pass over the concept of macro writing.
There is nothing magic or special in writing macros to extend CRiSP for your own personal desire. There is
a great sense of achievement in mechanizing some tedious editor task which has frustrated you in the past.
So in a sense, customizing the editor can have its appeal.
One of the major goals of the CRiSP macros is an attempt to achieve an object-oriented structured design.
(The term 'object-oriented' is used in a loose sense). When writing a macro to achieve something it is
desirable to ensure that the macro you have does not interfere with any existing macro. Also, looking to the
future, you need to ensure that other newer macros cannot affect the functionality of your macros. For a
normal high-level programming language, once you have written a program, debugged and compiled it, that
is it. Nothing can affect the correct behaviour of that program.
With an interpretive environment like CRiSP you are not in control. Rather, it is like being a guest in
someone elses house - you have to obey the rules or else you will get into a muddle. If we consider that the
environment of CRiSP, the macros and the binary, is one giant program, then in effect what you are doing
when writing your own macro is customizing the existing behaviour - you are interfering with the existing
code. The problems that can strike firstly is accidentally reusing symbol or function names which some other
macro is using, or creating private system buffers or files which some other macro also uses. Most of the
time when this happens, it isn't that difficult to figure out what is going wrong, but it is a nuisance that you
cannot code something and prove it is correct without considering the rest of the system.
When writing large C or C++ programs, split into multiple source files, you use programming conventions to
avoid such things as name space pollution, e.g. use of statically scoped variables and functions. CRiSP
encourages you to do likewise. Something that is declared static cannot be accidentally affected by some
other macro and gives you a sense of protection. Scoping and global vs. static are discussed in more detail
in the section on "Macros".
Another thing to consider before resorting to a private macro is to understand CRiSP, as a user, and its
philosophy. CRiSP is a complex piece of software when taken as a whole, yet it strives to create an easy to
use user interface for non-technically oriented people. Many people will use certain aspects of the software
and ignore or be totally ignorant of other aspects. What this means is that the functionality you are after may
already be there. If you have work to do and really find something annoying in CRiSP then by all means, go
ahead, and create your own personal macro(s). If you are going to spend a lot of time macro programming
then it is worth examining CRiSP as a whole because you may find useful code libraries or new ideas on
how to achieve things in a faster or more flexible way than any of your original ideas.
There are three things that are worth bearing in mind before embarking on writing your first macro:
1.Learn to use CRiSP properly. Do not be afraid to try things out, as otherwise you may not understand
what the supplied macros are actually doing.
2.Look at the sources to the macros which come with the CRiSP distribution. These not only implement
Page 9
the usability features of CRiSP but also contain useful real-life examples of various aspects of CRiSP
macro programming.
3.Where possibly, try and maintain a macro programming style. Do not write throwaway macros - treat
them with respect. For example, format them nicely, comment them, lay them out well. You will find it so
much easier to revisit your macros at a later date, or if you need to pass them on to people, they will
actually understand your code. Of course, this is what you should be doing with all your code, whether it
be CRiSP or C or Ada.
The macros supplied with CRiSP cover a lot of ground. These macros have evolved and grown over the
years. Some of these macros represent good solid examples of programming, others are not quite in the
same calibre. Many of these macros have evolved from an experiment to real use. So the coding styles are
not necessarily consistent. As each new version of CRiSP is released, new ideas are tried and the macros
are refined. As an example the capability to declare static variables and static functions is a relatively recent
addition to CRiSP. So not all functions which should be are marked with the static storage class specifier.
If you are planning to write a large complex macro, then the best recommendation is to start off with
something simple - experimenting at each step of the way, slowly refining and adding the required
functionality. This is how all of the functions in CRiSP have evolved. If you write a large macro without
experimentation, then you may find it very difficult to debug unless you have a good understanding of all the
information available. Although CRiSP looks and feels like a C interpreter, you should remember that it is
really an extensible file editor. By being very focused in your aims and expectations, you can know what to
look for.
Tools Overview
This section provides command line summary information of the various command line tools provided with
CRiSP. As a macro programmer, the tool you are most likely to use is the crunch compiler. If you are not
interested in writing CRiSP macros, then you are more likely to find the crtags tool of use to you.
cm(pg. 90).This is a low level macro language compiler. It is provided for completeness and
backwards compatibility with the older BRIEF .m style macros.
crppThis is the macro language preprocessor. It is very similar to a C or C++ preprocessor, but
is supplied as part of the macro compilation system, since it is not possible to rely on
customers having a C compiler installed on their system. It is not normally invoked
directly, but is used by the crunch compiler.
crunch(pg. 14). This is the main macro compiler which you can use to compile source files in the crunch
language (extension .cr) into compiled macro files (extension .cm). This is invoked
automatically if you use the <Alt-F10> key in CRiSP to compile the current buffer, or the
load command at the Command: prompt.
<Alt-F10>
crtags(pg. 10). This command is used to generate a cross reference tags file for source files. A number of
languages are supported. Although this command has nothing to do with macro writing (it
can be used on crunch macro files however), it is described in this document.
CRiSP provides a full user interface for manipulating tag files - creating tags, and using
the class browser window to view the objects defined in a user's project.
crtags: cross-referencing source files
crtags
normally used in conjunction with the 'vi' editor. A tags file is a file which contains a database of all language
specific constructs of source files, e.g. function definitions, type definitions, constants, etc.
The crtags program can be used to scan source files in various languages and produce a database file
listing the occurrences of important elements of the language, such as function definitions, constant
definitions, structures, classes, etc.
The supported languages are listed later in this section together with some application notes relating to each
language.
You can get a quick command line summary using the '-help' command line switch.
The basic command line syntax is:
Page 10
The crtags program is an enhanced tags utility loosely based on the Unix program 'tags' which is
crtags [switches] file1 file2 ....
You can use Unix style wild cards for the filenames, even on the Windows platforms.
The command line switches can be grouped into three types:
- general purpose switches affecting the crtags program,
- output file format switches
- language sensitive element switches
File formats
crtags creates a tags file. Two file formats are supported: text and binary. The binary format is smaller and
contains more information needed by CRiSP's cross-referencing facility (the class browser window). The text
file format was used in older versions of CRiSP and has now been deprecated.
File
formats:crtags
The binary file format is designed to be machine independent, meaning that if you share a network
filesystem, then the tags file can be used by all CRiSP clients on the network no matter what CPU
architecture they use.
Specifying Directories
If you specify the name of a directory instead of a file then crtags will recursively scan that directory for files.
This can be a quick and convenient way of handling entire projects of source code.
Language Mapping switches
crtags allows you to specify language mapping switches on the command line. This is designed to allow you
to configure how files with non-standard extensions are handled. By default, crtags treats files with certain
extensions as detailed below.
To set the language mapping mode, you can put something like this:
xyz=pascal
on the command line. Multiple switches can be used. The left hand side should match in case the
extensions to use. The right hand side should be one of the standard file extensions recognized or the
language name.
General switches
In general, it is recommended to use crtags without any command lines flags - the default options are
sufficient to create a browser tags file.
-aAppends the new tags information to the end of the specified tags file rather than
replacing it.
-allWhen parsing files, duplicate definitions are normally ignored. For example in a C source
file, you might have a prototype for a function and the function definition itself. If you
specify this flag then all occurrences of the object will be listed in the tags file.
-absoluteIf this switch is specified then the filenames in the tags file will be the full path to the file.
This can be useful when you have a large project which spans many directories and you
want to jump to arbitrary functions in any directory.
The down-side of this switch is that the tags file may be significantly larger than if the
abbreviated filenames are used.
This is the default.
-binaryCreate binary format tags file (default). The binary format allows for faster cross-
referencing in the CRiSP browser and supports structural information needed to display
the class browser window.
-dEnables debug. Not useful for the end user.
-helpLists complete summary of supported languages, switches and options.
-I fileThis switch allows you to specify the name of a file containing a list of filenames to be
Page 11
included on the command line. This allows you to generate a list of files and store them in
a file rather than being exposed to the command line limitations of certain operating
systems.
Multiple -I switches can be specified on the command line and can be intermingled with
normal files. If you use this switch is must be the last switch on the command line.
-ignorecaseWhen sorting the tags file during the output stage, ignore the case of data elements.
-len nnnSpecifies the length of the context line to include in the tags file. The default is 10, which
means that for each tag, not only is the line number within a file recorded but also a
portion of the matching line. This is designed to be used, because as files are edited, the
recorded line number may no longer exactly match the recorded tag. In this case, the
CRiSP tag macro will search for the line using the actual contents of the line.
Large values will significantly increase the size of the tags file, so there is a trade off
between speed and file size.
-l <lang>Treat all files on the command line as if they were of the specified language type. The
complete list of available languages can be seen using the '-help' switch, or consult the
sections below.
-nologoDo not print out the copyright logo message.
-OOptimise the tags output file. This only has meaning when used with the 'crtags' file format
and reduces the size of the tags file at the expense of readability.
-o <tags>Specifies the name of the output file to receive the tags database. The default value is
"tags" in the current directory.
-qQuiet mode. Do not display progress messages as files are parsed.
-sortTurns off the sorting of functions in the tags file.
-regexp <re>
+regexp <re>These two switches allow you to control the tag entries which are placed in the database.
If the -regexp switch is used then all entries which match the expression are not placed in
the file. If you use the +regexp switch then only entries which do match are placed in the
database.
regexp:crtags switches
-textCreate an ASCII (old style) index file. The text file is in a human readable file format, but is
not as efficient as the default binary format, and in addition it does not support information
needed for the CRiSP browser.
-uUpdate mode. Not currently implemented.
-wEnables warnings. Used for compatibility with Unix 'ctags' to show multiple function
definitions. Not particularly useful for larger projects where duplicate static functions may
exist in multiple source files, or where conditional compilation can cause two definitions for
the same function to be recorded.
-xCreate cxref style output. Not currently implemented.
Filename filtering
When performing recursive directory scanning, you may want to skip certain files or directories, for example
those used to store source code archives.
-xd <dirname>Specify directories to be skipped.
-xf <filename>Specify filenames to be ignored.
These switches can be specified multiple times as needed. When specifying directory names, only specify
the last component of the directory to be skipped, e.g. -xd SCCS to ignore all SCCS subdirectories.
Filename and directory specifications can include shell wild cards, such as *, [..] and ?. When specifying
these on a command line, you may need to quote the argument to avoid expansion interactions with the
command line shell you are using.
Output file format
Page 12
These switches allow you to specify the output file format.
-tagsOutput is compatible with the Unix ctags program and hence the tags database can be
used with other editors such as vi.
-textOutput file in textual format. Useful for diagnosing the output.
-crtags(Default) Proprietary file format designed to be used in conjunction with CRiSP. Although
this file format is liable to change in the future, it provides more information in the tags file
which at some point in the future will be used by CRiSP to provide a more user-friendly
interface.
Language specifications"
crtags contains a table of default file extensions and the languages they correspond to. If you need to
override these language formats, then use the '-l <lang>' switch to specify the language to be applied to ALL
files on the command line. (This switch is not position sensitive, it must precede all filenames on the
command line).
Language element feature selection
crtags supports a variety of languages. Different languages can describe different structures, e.g. a C++
program can contain class definitions, whereas an Assembly language program cannot. The data elements
which are currently parsed by crtags are listed below for each of the available languages.
You can use the -FEATURE switch to disable tag generation for some of these language features. For
example, you may not want #define entries in the tags file.
The following lists the elements you can disable, although not all of them are available for every language see the per-language description below for a list of entities supported.
-TYPEDEFDiscard type definitions (e.g. typedef's in C/C++).
-UNIONDiscard C style union definitions
Languages
In the following description, a summary of each of the supported languages is given, together with the
default file extension mappings and the tag entities which may be generated by crtags.
Note that crtags uses a fuzzy-parsing
advantage of working in spite of any syntax errors and avoids the complexity of worrying about compile-time
constant definitions which may affect the flow of the parsing (e.g. in the presence of #ifdef constructs).
The down-side of this is that the parsing may not be 100% correct as seen from the point of view of the
compiler. The aim is to provide a level of accuracy which makes the tool useful to you.
fuzzy parsing mechanism to scan source files. This has the
The CRUNCH Language
The crunch language is the language used to write macros for CRiSP. The crunch language looks and feels
a lot like the C language, and this should help users who are writing macros for the first time, but there are
significant differences, which the user should be aware of.
The CRiSP language supports a number of primitive data types(pg. 15).:
32-bit integers (int)
64-bit floating point numbers (float/double)
strings (string)
lists or arrays (list)
structs
CRiSP acts as an interpreter for the language. The programs which the user writes are first compiled to a
compact pseudo code format. Although CRiSP is designed to run as fast as possible and use as little CPU
resources as possible, the design of the interpreted language is aimed at keeping the size of the macros as
small as possible. Writing macros in the crunch language allows the internal architecture of CRiSP to be
extended and improved upon in the future whilst maintaining compatibility, at the source code level for user
written extensions to the editor.
The other advantages of writing macros in the crunch language is that the macros are totally machine
independent, working equally well on Windows or Unix platforms. Macros are also convenient when you do
not have access to a C compiler or other architecture specific development tools.
The crunch compiler is implemented using a full yacc grammar(pg. 25). of the ANSI C language, and
although many constructs may be accepted by the compiler, they may not generate any code, wrong code,
or cause the compiler to crash. When in doubt about the correct parsing of a macro, you should run the
crunch command with the -c flag. This will compile the source code into the .m intermediate language file
(the .m lisp-like language is CRiSP's assembly level code).
In order to write your own macros, you will need to understand various levels of detail. Writing simple one-off
macros is easy, but there are a lot of details to learn if coding up complex multi-file macro packages.
1.The syntax of the language. The syntax is very close to ANSI C. For those of you who know this
language, this means there is very little mental energy involved in understanding what to write or what
to expect.
2.The semantics of the language. This covers the actual context dependent meanings of constructs within
the language. This ranges from the meaning of a switch statement, to an understanding of the different
data and variable types.
3.The internal data types and objects within the CRiSP language. CRiSP supports objects ranging from
32-bit integer values, to entire buffers containing edited files, callbacks, dialog boxes.
4.An understanding of the macro primitives CRiSP provides. CRiSP provides numerous functions which
operate on internal data structures.
{button See Also, ALink(crunch,,,)}
The crunch compiler
The crunch program is the crunch compiler. It takes a source file (with .cr extension) and creates a .cm file,
Page 14
ready for loading into CRiSP. The crunch program uses its own internal preprocessor which is very ANSI-C
like. By doing this gives the user more portability of macros and avoids common differences between
standard preprocessors.
Next the intermediate file is converted directly to the binary output file. Crunch has a number of switches:
-cCompiles the source file to a .m file. This is useful for understanding the translation process
or to check for bugs in the compiler. If you have any problems understanding what crunch is
doing, then use this switch.
-DvarUsed to #define constants before preprocessing. This switch is passed directly to the
preprocessor.
-fUsed to flush output during debugging. Causes the output to be written to the terminal. This
is useful if crunch core-dumps and you want to try and ascertain at what point during code
generation the problem is occurring.
-IpathAdd a path to search for include files. This switch is passed directly to the preprocessor.
-gUsed to insert debugging information into the compiled code. This includes line number
information, so that when a macro error occurs, CRiSP can report the line in error.
-mThis is the make flag. Tests the modification time of the output file versus the source file and
only recompiles if it is necessary. This allows trivial makefiles to be built rather than having to
face the bugs in standard make. (See the distribution makefile how to use this).
For example, you can say:
crunch -m -o /macrodir *.cr
and only the out of date macro files will be recompiled. (No account is made of dependencies
on include files).
-nPrint out the names of files which would be compiled, but don't compile them. This flag is
useful with the '-m' (make) flag to verify what files will be recompiled.
-o file Specifies the name of the output file to create. The file parameter can be the name of a
directory in which case the output file is put into the specified directory.
-p cpp Used to specify the path of the C preprocessor to execute if the one on your system does not
conform to the standard used by the current Unix versions, e.g. if you are using Turbo C, or
you have a POSIX compliant C compiler.
-qIf more than one source file is specified on the command line, crunch normally prints the
name of each file as it is being compiled. This switch can be used to turn off this feature.
-SSpecial non supported feature. Used to dump a symbol table.
-UvarMake the named variable undefined. Passed directly to the C pre-processor.
-VPrints version number of compiler.
-#Prints each pass of the compilation process as it proceeds.
The crunch compiler more or less understands the full ANSI C syntax, including structure definitions, bit
fields and typedefs. However, crunch is really only designed to accept macros which can be used by CRiSP.
At present CRiSP cannot handle structure and typedef definitions and so it is best to avoid these.
The crunch compiler normally creates a temporary intermediate file between the pre-processing stage and
the compilation phase. Normally this file is created in /tmp. You can override this by specifying the name of a
directory in either the CRTMP or TMP environment variable. (CRTMP will take precedence if both are
specified).
{button See Also, ALink(crunch,,,)}
Data types
CRiSP supports a range of primitive data types and complex objects. The following is a summary of the
basic data types:
TypeDescription
Page 15
int(pg.
21).
float(pg.
21).
string(p
g. 21).
list(pg.
22).
declare(
pg. 25).
As well as these primitive data types, CRiSP also supports complex data types(pg. 38). used to refer to
particular instances of objects within the editor.
{button See Also, ALink(crunch,,,)}
32-bit signed integer.
64-bit floating point value.
Variable length string.
Arbitrary collection of objects, similar to an array. Lists may contain nested lists, and
can be used like a structure or array.
Used to define a polymorphic variable - one that can contain a value of any type.
Variables - types, scoping, argument passing
CRiSP supports a minimal set of data types necessary to allow sophisticated editing macros to be written.
Crunch requires that all variables to be used be declared before they are used. This is similar to the C
language, and is a useful feature since it avoids bugs being introduced due to spelling errors. The compiler
will complain about references to variables which have not been declared.
Although crunch may be classed as a fairly strongly typed language, it has mechanisms for processing
arbitrary variable types. For example, a macro could be written to return the minimum value of the
arguments passed:
int
min(int a, int b)
{
return a < b ? a : b;
}
This is fine, but doesn't allow the user to write a generic macro which can handle arbitrary variable types.
For example, if two strings were passed, maybe the shortest length string should be returned. This can be
handled by crunch with variables which are called polymorphic. The term polymorphic means that a variable
can have an arbitrary type and value. The type is dependent on its context. Originally, polymorphic variables
were added to facilitate the processing of lists, which are sequences of values of arbitrary type. To write a
more generic min() function, one could write:
declare
min(declare a, declare b)
{
if (typeof(a) != typeof(b)) {
error("Incompatible types.");
return -1;
}
switch (typeof(a)) {
case "integer":
case "float":
return a < b ? a : b;
case "string":
return strlen(a) < strlen(b) ? a : b;
case "list":
return length_of_list(a) < length_of_list(b) ?
a : b;
default:
error("Unknown type");
return -1;
}
}
Because the type of a polymorphic variable may change, CRiSP supports functions for determining the type
of the variable and macros can ensure that they don't attempt to perform an invalid operation.
Page 16
{button See Also, ALink(crunch,,,)}
Scoping
All variables created have a scope of visibility. CRiSP supports a number of scopes of visibility: static, local,
global and buffer-local. Global variables are always available to macros and retain their values from one
function to another. Local variables exist from the point at which they are defined to the end of the currentblock. The current block is defined as the current level of curly brackets. Static variables are variables which
are local to a function but which maintain their value across calls to the function. (This is identical to the C
mechanism). For example:
int fred = 99;
main()
{
string fred = "hello mum";
int i;
for (i = 0; i < 99; i++) {
list fred = quote_list(1, 2, 3);
}
}
In this example, there are three occurrences of the variable fred. The first one is a global variable, and is
assigned the value 99 when the macro is loaded. When the function main() is called, the global fred is
saved, and a new variable is created, of type string. The for loop demonstrates a new occurrence of fred
being defined purely for the scope of the loop. Within the loop, fred is a list. When the loop exits, the string
version of fred is accessible. Eventually when the function terminates, the integer value for fred is
accessible.
Internally, scoping is implemented by associating a block level with the definition of each variable. Global
variables are defined in block 0, which is never exited. Conceptually, each time an open curly bracket is
seen, a new level is entered. In the example above, the string version of fred is defined at block level 1.
When the close curly bracket is seen the block level is decremented and variables defined in that scope are
removed from the symbol table.
Because CRiSP is an interpreter, certain features of the language become available for very little interpretive
overhead. One of these features is dynamic scoping. Dynamic scoping is similar to the scoping rules of
Pascal rather than C. It is easiest to explain dynamic scoping together with an example:
int
func1()
{ int a = 1,
b = 2;
func2();
}
void
func2()
{
extern int a, b;
message("a=%d b=%d", a, b);
}
In this example, the function func2() is called from func1(). The declaration:
extern int a, b;
is used to tell the crunch compiler that the variables a and b will be accessible at run-time, even if there is no
definition of a and b within the current scope. Essentially, it just tells the compiler to not complain about
undefined variable references.
When the line:
message("a=%d b=%d", a, b);
is executed, CRiSP searches the current block level for a definition of the variables a and b. Since these are
Page 17
not found, CRiSP then searches the current block level - 1. At this block level, the definitions are found.
When a variable is accessed, CRiSP needs to locate the symbol definition dynamically. The order of
processing is as follows:
1.First a check is made for a static variable definition in the current function.
2.If no value is found as a buffer local variable, then CRiSP will try the current local variables of a
function.
3.If no value is found as a static variable then CRiSP will try a buffer local variable.
4.If no value is found in the current stack frame then CRiSP will search all the nested stack frames, back
to the outermost function call.
5.If no value is found then CRiSP will try for a global variable.
Note that it is possible to confuse CRiSP by declaring static variables inside local blocks (i.e. instead of at
the top of a function definition). This confusion can arise when a nested block and an outer block define
variables with the same name but with different attributes inside the nested block. Generally it is advisable
not to redefine variables within nested blocks with the same name as an existing variable in an outer block
to avoid any surprising results. (Note that this is only applicable to within a function; across function calls,
symbols may have the same name, so you do not need to know how a calling function is implemented).
These problems can arise because CRiSP is an interpretive language and doesn't necessarily assign unique
addresses to variables as might happen with a compiled language.
{button See Also, ALink(crunch,,,)}
Argument Passing
CRiSP supports a special form of argument passing, known as lazy evaluation. The arguments to a function
are not evaluated at the time a function is called (as it is in C). Instead they are evaluated at the time they
are referenced in the called function. This can lead to some difficult to understand code and hard to find
bugs, so it is important that the user understand this concept. This feature offers a lot of flexibility.
Before showing an example of this lazy evaluation scheme, it is necessary to discuss the mechanism used
to implement argument passing. Writing a function which takes parameters, and calling that function looks
and mostly feels like 'C'. For example, to define a function which takes three parameters, the first an integer,
the second a string, and the third a list would look like this:
int
func(int arg1, string arg2, list arg3)
{
...
}
The code above is treated as if the function was implemented as follows:
The user can pick either form for functions. Normally it is best to use the pure C style for function definitions,
and use the get_parm() primitive when a non-C compatible calling sequence is required, or for varargs
support.
For example, consider a macro which adds up all the integer parameters passed to it:
Page 18
int sum()
{ int arg_no = 0;
int sum = 0;
int arg;
while (get_parm(arg_no, sum) > 0)
sum += arg;
return sum;
}
Crunch performs limited prototype validation and this is designed to catch inconsistent coding errors. You
should therefore always specify prototypes for external functions so that CRUNCH can check that
arguments agree. (CRUNCH cannot perform a complete type-safe check because variables can be declared
as polymorphic, in which case the type of variable will not be known until run-time). In crunch, it is possible
to indicate that an argument may be optional. This is done by preceeding the type specifier with a tilde:
int
func(int arg1, ~list, string arg3)
{
...
}
This causes the variables arg1 and arg3 to be set up on entry to the function, but it is the functions
responsibility to get the value for the second parameter.
Given the above descriptions, it is now possible to understand the lazy evaluation scheme more easily.
Consider the following program:
find_strlen(string str)
{ int i = 0;
int len;
len = iterate_strlen(str, ++i);
/* At this point i is 1 greater than len. */
message("len=%d i=%d", len, i);
}
int
iterate_strlen(string str, ~int)
{ int len = 0;
int arg;
while (1) {
get_parm(1, arg);
if (substr(str, arg, 1) == "")
break;
len++;
}
return len;
}
In the call to the function iterate_strlen, the second parameter is specified as ++i. This does not cause i to
be incremented until it is referenced in the function iterate_strlen(). This occurs at the line:
get_parm(1, arg);
Lazy evaluation is used in the supplied macros mainly to allow specifying a private key mapping for pop up
windows. For example, the function select_buffer takes an optional parameter, (the 4th one), which is not
evaluated directly on entry to the function, but after the keyboard mappings have been set up. This allows
the calling macro to specify the name of a function to call to do whatever is necessary just before the user is
shown the popup window.
{button See Also, ALink(crunch,,,)}
Page 19
Variable argument lists
CRiSP supports a variety of mechanisms which allow for variable numbers of arguments to be passed to
macros. As described above the get_parm() and put_parm() primitives are used to access arguments to
macros. A useful addition to these primitives is the arg_list() primitive. This primitive returns a list
representing the arguments passed to the calling macro. This list can then normally be used as an argument
to further macros to allow for the fact that the called macro may have been passed an arbitrary number of
arguments.
In order to understand this clearly, let's take an example. Suppose we wish to write a macro which acts as a
wrapper around an existing primitive, e.g. the insert() primitive. The insert() primitive is used to insert text
strings into the current buffer. It can take an indefinite number of arguments, the first of which can optionally
be a printf-like formatting string. We could use the arg_list() macro like this:
example()
{
my_insert("hello %s", "world");
my_insert("%d+%d=%d", 1, 1, 1+1);
}
/* Our function -- note no arguments are specified */
/* in the definition. */
my_insert()
{
insert("[");
insert(arg_list());
insert("]");
}
In this example we access the variable number of arguments using arg_list() and thus pass on the
arguments to the insert() primitive without needing to write any macro code to get at and pass on the
arguments.
{button See Also, ALink(crunch,,,)}
Returning values and parameters
There are two ways to return values from a function: you can return a value as the result of the function, or
you can modify one or more of the calling parameters (as in pass by reference).
Returning values
To return a value, use the return statement. Functions can be declared as void, indicating that no value is
to be returned (i.e. the function is procedural). In which case, the return statement takes no argument.
Falling off the end of the function is the same as executing a return statement with no value:
void print_message(string str)
{
printf("%s\n", str);
}
int add2(int a, int b)
{
return a + b;
}
CRiSP also supports an older archaic function, returns, which acts like a function call and arranges a value
to be returned when the function exits. This function should be avoided where possible as it is not
guaranteed to work if any other function or primitive is called after it. This is present for backwards
compatibility only.
Pass by reference
Returning a value from a function using return probably accounts for 99% of the parameter passing
mechanisms used in the CRiSP macros. The alternative way to return values is pass by reference. The
syntax for this is:
Page 20
void get_max(int a, int b, int c, int& d)
{
d = max(a, b, c);
}
Note the ampersand after the type specifier for the last parameter in the list. Variables which are passed by
reference are noted by the crunch compiler and any assignments to these variables cause the right thing to
happen, i.e. the callers argument is updated.
The pass by reference mechanism shown above is actually implemented using the lower level put_parm()
primitive. Put_parm() is a special macro primitive which lets you assign values to the calling functions
parameters. The function takes two arguments - a number indicating which parameter to update and a
value. The above example could be written as:
put_parm
void get_max(int a, int b, int c)
{
put_parm(3, max(a, b, c));
}
The call-by-reference mechanism is new in CRiSP version 6, so many of the existing macros supplied with
CRiSP still use this older mechanism.
If you call the above function without specifying an appropriately typed variable for the return value, then you
are likely to get a macro error at run time or some other undefined behaviour. For example:
get_max(1, 2, 3, 4);
will result in an error because the 4 being passed is not a legal value to which CRiSP can assign a value to.
{button See Also, ALink(crunch,,,)}
The int data type
The int keyword is used to declare integer variables, i.e. variables which can hold only integral values.
CRiSP currently only supports 32-bit integers (i.e. chars and longs are not supported nor their
signed/unsigned counterparts). Integer variables are 32-bit twos complement numbers. The 32-bit word size
is chosen for maximum portability and usefulness.
Integer variables are used for many reasons -- as counters, indices, buffer identifiers, etc. The full
complement of C operators are supported for manipulating integer variables.
Integers are always stored in macros in a machine independent fashion. This means that compiled macros
using integer variables are portable to machines with different byte orderings.
{button See Also, ALink(crunch,,,)}
The float/double data type
The float and double data types are supported to facilitate implementation of macros which need to use
floating point numbers, e.g. the calculator macros, and the sum macro. CRiSP has no internal use for
floating point numbers.
CRiSP stores floating point numbers using the native C compilers double keyword, normally corresponding
to a 64-bit quantity.
Floating point numbers are declared using the float or double keywords. Currently these two keywords are
treated as being identical. It is recommended that users use the float or double keywords as appropriate to
the task in hand. Later versions of CRiSP may support a shorter floating point type for efficiency.
Floating point constants compiled into macros are NOT stored in a machine independent manner, and thus
compiled macros may not be portable to different machines.
Floating point numbers may be implicitly cast into integer values under certain circumstances.
{button See Also, ALink(crunch,,,)}
The string datatype
CRiSP supports a dynamic string data type. Strings variables may be used to store arbitrary length strings
(up to 64K on 16 bit machines and 4GB on 32-bit machines). Strings may be used to store any sequence of
characters, although storing the NULL character (ASCII 0) may cause problems, e.g. when determining the
Page 21
string length.
Storage for strings is dynamically allocated so no space needs to be preallocated for them.
Strings may be combined with the other data types to perform concatenation.
String constants are specified by enclosing the string within double quotes. For example
string:definition
"the help text"
You can include a double quote character by quoting it with a backslash as in:
"Select the \"Help\" button for more help"
You can use the backslash character to quote the meaning of the next character, e.g. a newline. Specify two
backslashes to get a single backslash. In addition, CRiSP supports the standard C style character
abbreviations for specifying newlines, backspace, etc.
If you have a long string literal, you can make formatting of the code more pleasing by using implicit string
concatenation. This is performed by specifying two string literals adjacent to each other. For example, the
following two examples are equivalent:
"The filename" "was not found."
"The filename was not found"
Alternatively you can use the string concatenation operator (+)string:concatenation to
achieve the same effect, but this is performed at run time rather than at compile time, and hence is slower.
{button See Also, ALink(crunch,,,)}
The list (array) datatype
CRiSP supports a number of data-types of which one of the most interesting and useful is the list data-type.
A list is an extensible data structure. A list can be used to group other data items together so that a single
variable can be used to refer to a whole collection of variables. In some instances in the CRiSP macro
support code, lists are used as if they were arrays (the syntax for referring to lists can use the same
notation). In other instances, lists can be treated like C structures.
A list is extensible, meaning it can grow as needed, e.g. by appending items to the end of it. A list can grow
to any size less than 64K bytes in total.
A list may be used to store any other data type, including lists. A list may be extended only at its end, by
appending data to it. Any element may be referenced by specifying its ordinal position in the list.
For example:
list lst;
/* Assign initial value to a list. */
lst = quote_list(1, 2, 3);
/* Now add something to it. */
lst[3] = "hello";
/* Now modify element in the middle. */
lst[2] = quote_list("abc", "def", 1.2);
List elements (or atoms), are indexed using a zero offset, i.e. the first element in a list is accessed as list[0].
Lists are first-class objects in the CRUNCH language. This means that many primitives can manipulate lists
or be passed lists where appropriate. As illustrated above, lists are declared using the list data
declaration. A list declared like this is akin to an array which is defined without an upper bound in C, but is
automatically extended as needed. Lists can be manipulated in many ways.
Attempting to access negative indices of a list/array will cause an integer value zero to be returned.
Attempting to access beyond the end of an array as an rvalue will return the value NULL. Attempting to
access beyond the end of a list/array as an lvalue (i.e. perform assignment to an element) will cause the list
to be padded with NULL values in the missing positions.
Page 22
The following sections describe various aspects of list management in more detail:
List assignment(pg. 23).
Making lists(pg. 23).
Manipulating list items(pg. 24).
Sorting lists(pg. 24).
Searching Lists(pg. 24).
Informational list primitives(pg. 24).
{button See Also, ALink(crunch,,,)}
List assignment
Assignment may be used to copy one list to another, or to clear out a list (thus freeing its internally allocated
storage). For example,
list lst1;
list lst2 = {1, 2, 3};
lst1 = lst2;
In this example, two lists are defined, the second of which is initialised at the point it is declared. The
statements:
list lst2 = {1, 2, 3};
and list lst2 = quote_list(1, 2, 3);
are equivalent. The curly-brackets initializor get translated by the CRUNCH compiler into the functional form.
The curly-bracket intializor format is more familiar and easier to grasp to C programmers, and is especially
useful when defining lists which contain sublists. For example:
list lst = { 1, 2, 3,
{40, 41, 42},
5};
defines a list with five elements in it, the fourth of which is a sub-list of three elements.
Lists can be built up into long data structures by appending data to them. The memory allocated to a list can
be freed simply by assigning the value NULL to it:
list lst = {1, 2, 3};
lst = NULL;
A null list is one whose length (as returned by the length_of_list primitive is zero). An uninitialised list is
implicitly assigned the value NULL.
{button See Also, ALink(crunch,,,)}
Making Lists
A list can be extended by simply using the binary operator '+'. For example, you can concatenate a single
item to the end of a list or add a new list at the end:
As well as defining lists piece-meal as shown above, lists can be created using two primitives -- quote_list()
and make_list(). quote_list() is a function which takes an arbitrary number of data types and returns a new
list. None of the arguments are evaluated.
Page 23
The make_list primitive is similar to quote_list() but each argument is evaluated in turn. For example:
In this example, the list qlst will contain two elements -- the number '1', and the string 'qlst'. Remember
that none of the arguments are evaluated. Care needs to be taken with this primitive to avoid confusion. If
the value '1' had been replace by the expression, '1+2', then the resultant qlst would still have a length of
two with the first element have a value of 3. If, however, the expression '1' above were to be replaced with a
nonsensical expression such as '1+qlst', then the result qlst would still be a list of length 2, but the first
element would be a sub-list representing the expression 'one plus qlst'.
Now consider the make_list() example. In this case, the assignment to mlst would be a list of length three,
because each argument would be evaluated in turn. In this case the expression Bqlst is a list of length two
as defined in the previous statement.
You should try and understand these two very useful and powerful primitives because in many cases in the
CRiSP macros, lists are passed around, either to represent static strings in a menu (in which case
quote_list() is called) or as an easy means to pass variable length arguments to a function in an extensible
manner (in which make_list() is normally used).
{button See Also, ALink(crunch,,,)}
Manipulating List Items
CRiSP provides a fairly rich set of primitives for manipulating list items. Individual elements in a list can be
accessed either with the function nth() or by using square brackets, which are normally easier to read.
(The CRUNCH compiler converts the square bracket notation to a call to the function nth()).
list lst = {1, 2, 3};
message("lst[2]=%d", lst[2]);
Note that indices to elements in a list are zero based. The example above would print the message
"lst[2]=3".
Individual elements or sequences of elements can be deleted using the primitive delete_nth(). This primitive
takes two or three arguments, the first of which is the list to operate on, and the second is the index of the
first item to delete. If the third argument is present then this can be used to indicate how many consecutive
elements to delete. For example:
list lst = {1, 2, 3, 4};
lst = delete_nth(lst, 2);
/* lst == {1, 2, 4} */
{button See Also, ALink(crunch,,,)}
Sorting Lists
A list of strings can be sorted into alphabetical order using the sort_list() primitive. The order of the sort can
be controlled for increasing or decreasing alphabetical order.
{button See Also, ALink(crunch,,,)}
Searching Lists
Lists can be searched for strings and regular expression, using the re_search() primitive. Any non-string
elements in the list will be ignored.
{button See Also, ALink(crunch,,,)}
Informational Lists
CRiSP contains various primitives which return lists as their return value. These primitives allow the macro
programmers to enquire about the state of various objects within CRiSP.
List of bookmarks (placeholders).
List of primitives built into CRiSP.
List of all symbols defined in a dictionary.
List of files matching a wild card pattern.
Get keyboard bindings.
List of all bitmaps and pixmaps in a .xpl file.
List of all buffer IDs.
List of all object dictionaries..
List of all defined keystroke macros.
List of all user defined dialog boxes.
List of all screens (peel off windows).
List of all windows in the current screen.
List of macros defined.
The declare datatype
The declare keyword is used to create a polymorphic variable. A polymorphic variable is one in which the
type of the variable stored can be changed. These are normally used as function parameters when it is not
known until run-time what the actual type will be, or for looking at elements in a list. The actual type of a
polymorphic variable is frozen when a new value is assigned to it, and until a new value is assigned the
function can treat the type of the variable as if it were of the type frozen. For example:
declare var;
var = 1.0;
var += 2.3;
/* var now contains value 3.3 */
var = "string";
var += "fred";
var = NULL;
/* Variable contains no value. */
{button See Also, ALink(crunch,,,)}
Structures
CRUNCH supports a minimal 'struct' facility. A structure is represented internally as a list but the usual X.Y
syntax allows convenient access to elements of a list/structure without having to manually #define indices.
Structures can be nested as in C. The order of definition of members of a structure is used to access
particular indices into a list structure. There is no concept of 'structure' padding as a CRUNCH structure is
not directly mapped on to a memory block.
{button See Also, Alink(crunch,,,)}
Language Grammar
The CRUNCH language is very similar to ANSI C. The following sections describe features of the language
grammar:
There are two main types of declarations -- function definitions and data declarations. A function definition
defines the body of a function. A data declaration is used to declare global variables or specify prototypes. A
data declaration has the form:
This is similar to C. The storage_class_specifier is used to identify how the variable is to be stored. The
currently supported and meaningful storage class specifiers are:
externThe variable is defined somewhere else. References to the variable will be validated
against its type information, but no code will be generated to create the variable. This is
typically used to implement a forward reference mechanism.
staticWhen applied to a macro definition (function definition), the macro can only be invoked
from a macro defined within the same file. The macro function will not be visible or
accessible to any other macros or for use in callbacks. The use of the static keyword is
encouraged for all functions which are part of an implemented feature but of no use to
other functions, and also for functions which are not going to be called back, e.g. as a
result of a trigger, or keystroke.
When applied to a variable defined within a function, static has the same meaning as in
the C language, viz. the variable will maintain its value across function calls. A static
variable can be initialised, in which case the function will be initialised the first time the
function is called.
Type specifier should be one of the following:
intUsed to define a variable which will store a 32-bit integral value or to specify a function
which returns an int value.
float
doubleUsed to define a variable which will store a 64-bit floating point value or to specify a
function which returns a float value.
stringDefines a variable which can store an indefinite length string or a function which returns a
string value.
listDefines a variable which can store a list value or a function which returns a list value.
declareDefines a polymorphic variable which can store any data type, or a function which can
return any type.
voidUsed to indicate a function which doesn't return a value.
A declarator is defined as one or more variable names, or function prototypes separated by commas.
An initializor is used to give a variable an initial value, similar to C. Initializors may be arbitrary expressions,
i.e. they are not limited to constant expressions, even for global variables. Lists may be initialised somewhat
similarly to C structure initialisors.
For example:
extern list fred;
int func (int, string, string);
int a, b = 1;
Page 26
list l = {
"Item-1", {1, 2, 3},
"Item-2", "hello mum",
"Item-3", 3.14159, /* Trailing comma optional */
};
The storage_class_specifier is currently ignored, although a future version of the language will be able to
understand the static class specifier. The type_specifier is used to indicate the return type of the function.
This is currently ignored.
The argument list should be specified in the ANSI-C format, specifying the type specifiers and optional
names. In addition, crunch supports the syntax:
~ type_specifier [name]
which acts as a place holder for an optional argument or an argument which is to be handled with different
semantics from the standard C-style.
Crunch also supports the ellipsis (...) to indicate that optional further arguments may be specified.
Please note that crunch does not currently check function calls against prototypes.
{button See Also, ALink(crunch,,,)}
Loading a macro: main, _init
As explained below, global variables may be initialised with non-constant expressions, unlike C which is
limited to a constant expression. Because of this facility, CRiSP provides a mechanism for ensuring that
these global variables are initialised before the macros execute. All global variable definitions and initializors
are compiled into a function called _init. When a compiled macro file is loaded (the .cm file), this macro is
executed first. Programmers can put their own one-time initialisation code in the function main(). All the
code in main() is executed within the context of the function _init after the global initialisations. To
understand this better, it is best to compile your code with the -c switch and look at the lisp code that the
compiler generates.
{button See Also, ALink(crunch,,,)}
Expressions
The following table summarises the operator precedence and associativity of the primitive elements of an
expression. This table is a copy of the table which can be found by executing the hier macro at the
command line prompt:
Arity Operator Assoc
-------------------------------------------------------------binary () [] -> . l -> r
unary ! ~ ++ -- - (type) * & sizeof r -> l
binary * / % l -> r
binary + - l -> r
binary << >> l -> r
binary < <= > >= l -> r
binary == != l -> r
binary & l -> r
binary ^ l -> r
Page 27
binary | l -> r
binary && l -> r
binary || l -> r
ternary ?: r -> l
binary = += -= *= /= %= >>= <<= &= ^= |= r -> l
binary , l -> r
------------------------------------------------------------- From K&R, p 49
In the above table, the following are not supported: ->, . (dot), (type), sizeof. Also, crunch does not support
structures, unions, pointers, or explicit dereferencing.
Crunch supports a fair amount of automatic type co-ercion. The following table lists the coercion rules when
used with the arithmetic operators only. (Type coercion is not performed for function arguments). The prefix
character is used to identify the type of the variable or expression - i = integer, f = float, l = list, s = string, a =
any type. sym is used to denote a symbol of the specified type; expr is used to denote an expression
evaluating to the specified type. (Note that these conversions are implicit -- the typecast on the right hand
side is not a supported feature, currently).
isym op= fexpr => iexpr op= (int) fexpr
fsym op= iexpr => fsym op= (double) iexpr
lsym += aexpr => append aexpr to end of lsym
fsym++ => fsym += 1.0
fsym-- => fsym -= 1.0 etc..
iexpr op fexpr => (double) iexpr op fexpr
fexpr op iexpr => fexpr op (double) iexpr
iexpr + sexpr => "iexpr + sexpr"
(string concatenation)
fexpr + sexpr => "fexpr + sexpr"
(string concatenation)
aexpr + lexpr => new list with aexpr at front
Basically, a string plus a number (or vice versa) converts the number to a string and performs string
concatenation. A list plus any type creates a new list by concatenating list and value. An integer and floating
point value when combined results in a floating point value. Using these rules, a value of one type can easily
be converted to a value in another type:
string s;
int ival;
ival = 99;
s = "Value: " + val + " brass monkeys";
/* s = "Value: 99 brass monkeys" */
ival = 1000000;
s = "Value: " + (0.0 + ival) + " big macs";
/* s = "Value: 1e+06 big macs" */
When converting floating point numbers to strings, the "%g" printf format specifier is used.
{button See Also, ALink(crunch,,,)}
Loop constructs: for, while, do
There are three main looping constructs, the for loop, the while, and the do loop. The for loop is a
generalised looping mechanism supporting the following syntax:
for ( init-expr ; while-expr ; post-expr )
statement
Page 28
Loading...
+ 64 hidden pages
You need points to download manuals.
1 point = 1 manual.
You can buy points or you can get point for every manual you upload.