Crunch CRiSP File Editor 6 User Manual

CRiSP File Editor Programmers Guide
Version 6
© 1998 Foxtrot Systems Ltd
www.pacemaker.co.uk sales@pacemaker.co.uk fox@crisp.demon.co.uk
Page 1
77
An Overview of the Programming Facilities 88
Writing your own macros 99
Tools Overview 111100
crtags: cross-referencing source files 10
File formats 11 Specifying Directories 11 Language Mapping switches 11 General switches 11 Filename filtering 12 Output file format 12 Language specifications" 13 Language element feature selection 13 Languages 13
77
77
88
99
00
The CRUNCH Language 111144
The crunch compiler 14 Data types 15 Variables - types, scoping, argument passing 16 Scoping 17 Argument Passing 18 Variable argument lists 20 Returning values and parameters 20
Returning values 20
Pass by reference 20 The int data type 21 The float/double data type 21 The string datatype 21 The list (array) datatype 22
44
Page 2
List assignment 23 Making Lists 23 Manipulating List Items 24 Sorting Lists 24 Searching Lists 24 Informational Lists 24 The declare datatype 25 Structures 25 Language Grammar 25 Declarations 26 Function definitions 27 Loading a macro: main, _init 27 Expressions 27 Loop constructs: for, while, do 28 Testing expressions: if 29 Selection: switch 29
Debugging macros 333300
Primitives for showing output 30 Macro tracing 31 Debug applied to a function 32 Debug on startup 32 Debug buffering 33 The vars() macro 33
Buffers, Files and Windows 333333
Buffer Attributes 34 Ansi Mode buffer attribute 34 The Backup Flag 34 The Binary Flag 34 Buffer Contents 35
00
33
Buffer name, buffer ID and File name 35 The Carriage-Return flag 35
Page 3
Current cursor position 35 Modified Flag 35 Permissions & Read-only Flag 35 Process Buffers 36 Region Markers 36 Symbol Table 36 System Buffers 36 Tab Settings 36 Undo Information 36 Character Maps 37
Objects supported by CRiSP 333388
File Types -- Text files and Binary Files 39
Backing up Files 39
Autosaving 39
Core dumping 40
Regions and markers 40 Macros 41
Global and Static macros 41
Modules and static macros 42 Registered Macros 42 Timer functions 45 Color Support 46
Searching for text -- Regular Expressions 444477
Character Escaping 48 The wild card operators: ? and * 48 Character Class: [..] and .. 49
88
77
Matching Line boundaries 49 Repetition: @ and + 49 Regular Expression Grouping: .. 50 Minimal and Maximal Matching 50 Matching Direction 51 Regular Expression Syntax Mode 51
Page 4
GUI Based Objects 555511
Terminology 52 Windowing Systems Programming 52 Overview of the Dialog box system 53 Overview of an Object 54 Creating a Dialog Box 55 Object Types 56 Geometry Layout 59 Resizing dialog boxes, and Constraint management 60 Object hierarchies: menu bars, tool bars, status panels 62 Object hierarchies and grouping 63 Sub-groups 64 Groups and the DBOX_CONTAINER object 65 Properties (private symbols) 65 Dialog boxes and Callbacks 66
11
Platform Specific Issues 67
How to create your own Colorization file 666677
Keyword Builder 68
Character classes 69
Global attributes 69
Keywords 69
Creating a new colorizer 69
Dialog box buttons 69 .KWD Keyword File Format 70
Comments 70
Section Name 70 The flags= Directive 71 Character Class Directives 72 Keyword Directives 74 Keyword Flags 75 Keyword Regular Expressions 76
77
Limitations of Colorization 76 Case study #1: C colorizer 77
Page 5
Case study #2: Fortran colorizer 78
Interprocess Communication and CRiSP 777799
IPC Mechanisms 79 IPC Primitives 80 IPC Callbacks 80 TCP/UDP Communications 81 Pipe Communications 84 PTY Communications 84 DDE Communications 85 Signals Communications 86
Keyboard objects 888877
CRUNCH: Things to watch out for 999900
The cm compiler 999900
99
77
00
00
Page 6
.
Introduction
Document version: 1.002, date 24 November 1997
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
OptionsStartupStartup 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.
crpp This 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.
-a Appends the new tags information to the end of the specified tags file rather than
replacing it.
-all When 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.
-absolute If 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.
-binary Create 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.
-d Enables debug. Not useful for the end user.
-help Lists complete summary of supported languages, switches and options.
-I file This 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.
-ignorecase When sorting the tags file during the output stage, ignore the case of data elements.
-len nnn Specifies 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.
-nologo Do not print out the copyright logo message.
-O Optimise 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.
-q Quiet mode. Do not display progress messages as files are parsed.
-sort Turns 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
-text Create 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.
-u Update mode. Not currently implemented.
-w Enables 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.
-x Create 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.
-tags Output is compatible with the Unix ctags program and hence the tags database can be
used with other editors such as vi.
-text Output 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.
-CLASS Discard class definitions
-CONST Discard constant variable definitions (or parameter definitions)
-DATA Discard definitions of global variables.
-DEFINE Discard #define definitions.
-ENUM Discard enum definitions
-ENUM_MEMBER Discard the members of an enum definition.
-FUNCTION Discard function definitions
-INDEX Discard SQL index definitions.
-LABEL Discard label definitions (targets of a goto statement).
-MEMBER Discard structure/union/class member definitions
-MODULE Discard module definitions.
-PACKAGE Discard package definitions.
-PROCEDURE Discard procedure (subroutine) definitions
-RULE Discard SQL rule definitions.
-STRUCT Discard C style structure definitions
-TABLE Discard SQL table definitions.
-TRIGGER Discard SQL trigger definitions.
-TYPEDEF Discard type definitions (e.g. typedef's in C/C++).
-UNION Discard 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.
Language File extensions
Ada .a Assembler .asm, .s Basic .bas C.c C++ .C, .c++, .cpp, .cxx, .h, .h++, .hxx CRUNCH .cr Fortran .f, .fcm, .f90 HTML .html, .htm IDL .pro Java .java Pascal/Borland Delphi .pas
Page 13
SQL .sql TeX .tex Perl .pl, .pm Verilog .v, .verilog VHD .vhd, .vhdl Yacc grammars .y
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:
-c Compiles 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.
-Dvar Used to #define constants before preprocessing. This switch is passed directly to the
preprocessor.
-f Used 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.
-Ipath Add a path to search for include files. This switch is passed directly to the preprocessor.
-g Used 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.
-m This 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).
-n Print 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.
-q If 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.
-S Special non supported feature. Used to dump a symbol table.
-Uvar Make the named variable undefined. Passed directly to the C pre-processor.
-V Prints 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:
Type Description
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 current block. 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:
int func() { int arg1; string arg2; list arg3;
get_parm(0, arg1); get_parm(1, arg2); get_parm(2, arg3);
... }
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:
list lst; lst += 1; /* lst == {1} */
lst += "hello"; /* lst == {1, "hello"} */ lst += lst; /* lst == {1, "hello", 1, "hello"} */
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:
list qlst. mlst;
qlst = quote_list(1, qlst); mlst = make_list(1, qlst);
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.
Page 24
Primitive
bookmark_list command_list dict_list file_glob key_list list_of_bitmaps list_of_buffers list_of_dictionaries list_of_keystroke_macros list_of_objects list_of_screens list_of_windows macro_list
{button See Also, ALink(crunch,,,)}
Description
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:
Declarations(pg. 26).
Page 25
Function definitions(pg. 27). Expressions(pg. 27). Loop constructs(pg. 28). Conditional test - if(pg. 29). Selection: switch(pg. 29).
{button See Also, Alink(crunch,,,)}
Declarations
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:
[storage_class_specifier] [type_specifier] [declarator [= initializer]] ;
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:
extern The 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.
static When 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: int Used to define a variable which will store a 32-bit integral value or to specify a function
which returns an int value.
float double Used to define a variable which will store a 64-bit floating point value or to specify a
function which returns a float value.
string Defines a variable which can store an indefinite length string or a function which returns a
string value.
list Defines a variable which can store a list value or a function which returns a list value. declare Defines a polymorphic variable which can store any data type, or a function which can
return any type.
void Used 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 */ };
{button See Also, ALink(crunch,,,)}
Function definitions
A function definition has the form:
[storage_class_specifier] [type_specifier] function_name ( [arg_list] ) { function_body }
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