Skip to content

Latest commit

 

History

History
93 lines (81 loc) · 4.49 KB

INTERNALS.md

File metadata and controls

93 lines (81 loc) · 4.49 KB

Internals

Macros

There are regular macros EMACRO/SMACRO/MACRO (expression/statement/expression+statement macros) and vararg macros (EMACRO'/SMACRO'/MACRO'). Regular macros take a function that takes an attrset with __state__ (and potentially other values) and returns compiled code (potentially using compileExpr and compileStmt). Vararg macros additionally take a variable amount of arguments which get passed in __args__.

There are also let macros LMACRO. They take a function that takes __state__ and __vars__ (and potentially other values). __state__ is explained above, __vars__ has a type [{ name : string, value : expr }]. The function must return [{ code : string, expr : expr, local? : bool, predef? : bool }], where code is the raw code used to initialize the variable, expr is whatever is returned to the user (could be simply RAW var.name, could be something else). local is an optional flag indicating the value should be local (defaults to true), predef means the value should be declared before setting every value (defaults to false).

Each macro may have arbitrary data added to it by doing macro // { ... }. This way you can pass additional parameters to macros, or specify the type info (see the below section).

For example, Vararg macros are implemented as regular macros with __functor set to a function that extends self.__args__ with the provided argument.

Type info

Types flow in one direction. After a value has been defined, its type can't become more specific - that would require complex type inference algorithms which are out of scope for this project.

Each expression (including macros) might have the following attrs:

  • __kind__ - either rawStdlib, custom, customStmt, customExpr (rawStdlib is used for importing typedefs, custom is macros). If __kind__ isn't set, it's considered a normal table.
    • For JSON type definitions, a special value rec of __kind__ is allowed to specify the fact __pathStdlib__ recursively refers to another entry in the JSON. In Nix, rec isn't allowed.
  • __pathStdlib__ - if __kind__ is rawStdlib, then this contains the code needed to access the expression.
  • __validVar__ - the value is a valid var (i.e. can be assigned to via lhs = rhs statement). Additionally, expressions with __kind__ = "rawStdlib" are always considered valid vars since they currently can't be results of function calls, only property and index accesses.
  • __prefixExp__ - the value is a prefixexp as per Lua reference (valid vars also count as valid prefixexps)
  • __wrapSafe__ - the expr doesn't need parens when used with binary operators (prefixexps also dont get wrapped with parens)
  • __type__ - the Lua type of the result of the expression's evaluation (A string - boolean, number, table, nil, function, etc)
  • __meta__ - metatable
  • for functions:
    • __minArity__ - specifies min arity (arg count)
    • __maxArity__ - specifies max arity (if this is set, __minArity__ must be set as well)
    • __retType__ - specifies the return type
  • for tables:
    • __entry__ - specifies the default entry type
      • __pathStdlib__ must be set to an empty string, and subproperties must have __pathStdlib__ set to just the property path in relation to the entry (for example, if a table contains other tables with a subtable named x which contains y, the type definitions could look like this: {..., __pathStdlib__": "", "x": {..., "__pathStdlib__": "x", "y": {..., "__pathStdlib__": "x.y"}}} )
    • __list__ - a special attribute indicating values from numeric table keys.
    • The other attrs are used for table property access. This is the reason the other attributes are surrounded with __ - the names could clash otherwise.

An expression's type is considered to be its attributes __retType__, __type__, __minArity__, __maxArity__, __entry__. For Nix expressions (function, strings), type is autogenerated.

For checking that b's type equals a's type, each property of b's type must either match that of a's, or either a or b's type should be missing that property.

If __meta__.__call is set or __type__ is function, a __functor attribute is generated to call CALL. In case of __call, function arities get taken from __call.

All in all, there are many internal functions involved in creating macros. Reading the compiler code should be enough to get an idea of how to create macros.