Skip to content

Module definition language definition

Ian Ross edited this page Oct 1, 2013 · 4 revisions

Overview

Fairly basic for now, and a lot of it is just a bit of a braindump. I'll write a proper language reference once I know better what things are going to look like.

The idea is, as far as possible, to make the language declarative: say "what" not "how". I think that this should work even for the more complex examples involving choice and repetition. (This is thinking just of surveys for the moment...)

Lexical rules

Layout

Use the same "optional layout" approach as Haskell so that layout converts to { ... ; ... ; ... } on lexing. I'll work out the detailed rules for this as part of the grammar.

(I eventually want some sort of macro/syntax definition facility, so this might have be revisited when I implement that.)

Identifiers
  • In general, identifiers are composed of a period-separated sequence of identifier components, each of which begins with a letter and is composed of letters, digits, _ (underscore) and - (hyphen). Examples: oP.Examples.MoodSurvey, Standard.MontrealCogntiveAssessment, ClinicA.Admin.PatientIntake.

  • Some identifiers are reserved as keywords.

  • Both keywords and user-defined identifiers are case-sensitive: all keywords are lower case.

Literals
  • Numeric literals: 123, 1.4, -0.5E-6, 25%: all numeric values are just treated as "numbers", i.e. there's no semantic split between integer and floating point values -- if a float is used in a context where an integer is needed, we'll just round.

  • String literals: "abc", "Hello, \"Bob\"!" (Escape sequnces are \" for double quote and \[0-9]+ for Unicode -- need to think about this a little).

  • Boolean literals: true, yes; false, no (all options equivalent when boolean value required).

  • Vector/array literals: [ 1, 2, 3 ], [ true, false, false ] (heterogeneous). Equivalent to [ 0 => 1, 1 => 2, 2 => 3 ], i.e. treated the same as maps/records.

  • Map/record literals: [ a: 1.2, b = "abc", "11XC5" => false, 2: "x" ] (keys may be identifiers, strings or numeric, separator can be =, : or =>).

Grammar

Expressions

The usual kind of thing:

  • Numeric operators: +, -, *, /, ^, floor, ceil, abs;

  • Comparison operators: ==, /=, <=, >=, ==@i, /=@i, <=@i, >=@i (the @i things are case folding comparisons -- just experimenting with possible syntax for these);

  • Boolean operators: and, or, not, any, all;

  • Function applications: f(1, 2), myFunc(x, "abc"), etc.

Definitions
  • General language features:

    1. Functions: parameter passing using call-by-value or call-by-reference? Allow both, but require call-by-reference to be explicitly flagged? (Initial approach: functions all pure call-by-value only, mostly just to have them in place to play with and think about later.)

    2. Macros: could be useful (see below in Questions), but need to think about the details -- some sort of backquote-like templating scheme?

    3. Modules:

      • What can go in a module?
      • What happens when a module is activated? (Distinguished "Main" component?)
    4. Enumerations: basically just a way to map between identifiers and some numeric or textual representation.

  • Application-specific features:

    1. Components: SurveyPage (a collection of questions), Question, function definitions, etc.;

    2. Assets: image, video, audio;

    3. Translations:

      • Base language to other languages;
      • Templating/functional approach for pluralisation and word order issues.
    4. Help/description text: probably using some sort of message catalogue approach -- this plays nicely with translation;

    5. Module metadata: simple key/value block (like a COBOL DESCRIPTION SECTION. (!)).

Rendering and data collection semantics

Examples

Questions

Defining question/component types

What about mechanisms for users to define, e.g. new question types? I'd thought of having a macro-like Specialisation construction where you would be able to define a question/component type that provides a subset of the functionality of an existing one (as a really simple example, you could define a YesNoQuestion as a specialisation of a more general ChoiceQuestion), but that's quite restrictive.

To define general components though, you need to be able to represent the HTML and JavaScript templates that are generated when the component is rendered, which is getting way too detailed for most users.

Other ideas for later

Namespaces
  • Modules live in namespaces, e.g. base, openPsych, openPsych.Tests, ClinicA, etc.

  • Within each namespace, module names can be considered hierarchically, e.g. SubstanceAbuse.MoodSurvey, but this is not necessary.

  • Naming: <namespace>:<module> gives namespace explicitly, e.g. ClinicA:Utils.Logo, openPsych:Tests.StroopTest; without namespace, a module name references the first matching module in a namespace search path. (Or a better way to think of it is perhaps a bit like Plan9's union directories, where you basically overlay all the modules defined in all the namespaces so you end up with what looks like a single module hierarchy, but you can easily override things as you want. For example, there might be a base:Styles.Fonts module that defines font names to be used for rendering components. If you define a ClinicA:Styles.Fonts module and use the ClinicA namespace, then the definitions in the latter module will override the defaults, whatever they might be.

  • A namespace is essentially a record containing modules; a module is essentially a record containing definitions of various kinds.

  • Defining modules:

-- Define the namespace for all further definitions in the file:
namespace openPsych

-- Define a module:
module Simple.MoodSurvey
   ...
  • Setting namespace "search path":
use namespace ClinicA as A
use namespace openPsych

or

use namespace ClinicA as A, openPsych as oP
  • Namespace base available by default.

  • Namespaces searched in order given in use namespace declarations (or stacked in a union in that order, so the last use namespace gives the "topmost" namespace that you see before any of the others).

  • Aliasing with use namespace ... as ....

  • Parameterisation over namespaces:

module Simple.MoodSurvey(styleNamespace)
  use namespace styleNamespace
  ...

which can then be used as

use namespace ClinicA
Main = Simple.MoodSurvey(ClinicA)

or something like that...