Creating custom languages
WARNING: Creating a customized language involves paying close attention to a large number of details and handling correctly a large number of potential submission issues students can have. Code carefully, and use a provided language if possible!
BitterSuite3 provides the ability for course accounts to provide their own particular languages that are useful for particular assignments, or particular aspects of the course that are tested on multiple assignments but are likely not of use to other courses. This behaviour is triggered when the expression
(language mylang)
is read from an options file; the file the behaviour is read from is located at
/u/course/bittersuite_languages/mylang/definitions.ss.
General features of the definitions file
The file must provide four functions:
- (initialize hash-table)
- (parse-option hash-table key . values)
- (interpret-file hash-table file)
- (run-test hash-table)
The value produced by initialize is ignored; its purpose is solely to mutate the hash-table passed to it to provide an initial state for the requested language.
The parse-option function first checks to see if it understands the key that was passed to it. If not, it returns 'not-handled (unhandled-marker from hierarchy-runner/common) and allows the default BitterSuite behaviour to determine any appropriate action for the given key. If it does, it next checks the values to see if they conform to expectations. If not, an error is flagged by returning the symbol 'bad-value (provided by hierarchy-runner/common as the binding bad-value-marker). If it does, then the state of the hash-table should be changed to reflect the option request and the value 'handled (handled-marker) is returned. Good practice is to simulate namespaces for the various languages by prefixing a language marker to any symbols inserted into the hash table as keys.
The interpret-file function is passed a file that was found in the current directory. If the language does not know what to do with the file, it should return 'not-handled (unhandled-marker) and defer to the base code. If it does know, then it should utilize the file in any language-appropriate fashion, update the hash-table to reflect any changes of state, and return 'handled (handled-marker).
The run-test function is called after parse-option and interpret-file in any leaf directories of the hierarchy. It should perform a test of the student code that it is directed to by the current state of the hash-table. This function returns two values. The first is either a number representing the percentage earned on the given test, or the symbol 'defer if the test's output is to be examined later. The second value is a status string explaining the mark earned; it may be the empty string.
Making use of the common module
When the module is loaded, it is loaded in a context where the base BitterSuite code is in its search path. This means that the definitions module can require hierarchy-runner/common, which provides a number of conveniences:
- The
current-verbosity-threshold
parameter. This should be set at every entry point into the language's namespace (ie, the functions listed in the previous section) to the verbosity level specified in the hash table: (parameterize ([current-verbosity-threshold (hash-ref ht current-verbosity)]) ...{code}...)
- The
cond-print
function. This should be used exclusively in place of functions like printf. It controls the amount of output the end user sees based on the verbosity setting; lower verbosity levels should be used for core, fundamental information while higher verbosity levels should target information that's likely more useful for debugging. The function is called as (cond-print min-verbosity fmt-string . args)
, where min-verbosity
specifies the minimum level that current-verbosity-threshold
must be set to in order for this information to be output, and fmt-string
is either displayed directly or passed to printf with args
as extra parameters if args
is non-empty.
- A number of convenience identifiers that specify various hash-table keys which, unlike direct symbols, can be checked for correctness by Racket while loading instead causing errors in the middle of a test run.
Coding with DrRacket
Unfortunately, DrRacket's syntax checker will complain that no such module is available in the search path if you make use of hierarchy-runner/common. One way around this is to make a local copy. From your own personal directory on your course account, issue the command
cp -r /u/isg/bittersuite3/runTests-files/hierarchy-runner .
Then, from Finder, copy the directory from the course account to a subdirectory of a directory similar to the following on your own personal account:
$HOME/Library/Racket/5.0/collects
where, at a minimum, the numbered portion of the path must agree with the current version of Racket. The path conventions may also change in future versions of PLT Racket.
Note also that this suffers from a staleness problem; if the common module is updated in a way that impacts your language code, you will need to recreate your local copy.
Providing a computeMarks-postprocess executable
If this file is in the language's directory (same as
definitions.ss
), it will be executed at the end of
computeMarks
. Typically, this is done to keep any additional language-specific files for the student output. Care must be taken to include all files that will be relevant, and only to include files that exist in the student's output directory. See the man page for
keepFile
for a description of all of the options.
Pitfalls