I have found that it is convenient to create separate stacks for every type.
I found that I need only 5 basic operations and that I can generate/compile/transpile code to enact these operations.
The "syntax" for talking about types in this manner, drops to something very small (bizarrely, the number 5 comes up again - I can describe the new syntax using only 5 productions).
I describe this method in the following essay.
id = { ... } --> class with fields def
id = :string --> string def
id = :map --> map def
id = | ... --> or type def
id = '...' | '...' | ... --> enum def
[This syntax was chosen for its regularity since it was originally parsed with manually written code. Each construct has a left handle. Every construct begins with "id = ", then the next character determines the kind of construct, e.g. "id = {" means that the construct is a class with fields, "id = '" means enum, and so on.]
For a prototype of this, see https://github.com/guitarvydas/stack-dsl
[comments deleted, for full contents see https://github.com/bmfbp/bmfbp/blob/main/build_process/esa-transpiler/exprtypes.dsl]
esaprogram = { typeDecls situations classes whenDeclarations }
typeDecls = :map typeDecl
situations = :map situationDefinition
classes = :map esaclass
whenDeclarations = :map whenDeclaration
typeDecl = { name typeName }
situationDefinition =| name
esaclass = { name fieldMap methodsTable }
whenDeclaration = { situationReferenceList esaKind methodDeclarationsAndScriptDeclarations }
situationReferenceList = :map situationReferenceName
situationReferenceName =| name
methodDeclarationsAndScriptDeclarations = :map declarationMethodOrScript
declarationMethodOrScript =| methodDeclaration | scriptDeclaration
methodDeclaration = { esaKind name formalList returnType }
scriptDeclaration = { esaKind name formalList returnType implementation }
returnType = { returnKind name }
returnKind = 'map' | 'simple' | 'void'
formalList = :map name
esaKind =| name
typeName =| name
expression = { ekind object }
ekind = 'true' | 'false' | 'object' | 'calledObject'
object = { name fieldMap }
fieldMap = :map field
field = { name fkind actualParameterList }
fkind = 'map' | 'simple'
actualParameterList = :map expression
name = :string
methodsTable = :map declarationMethodOrScript
externalMethod = { name formalList returnType }
internalMethod = { name formalList returnType implementation }
implementation = :map statement
statement =| letStatement | mapStatement | exitMapStatement | setStatement | createStatement | ifStatement | loopStatement | exitWhenStatement | callInternalStatement | callExternalStatement | returnTrueStatement | returnFalseStatement | returnValueStatement
letStatement = { varName expression implementation }
mapStatement = { varName expression implementation }
exitMapStatement = { filler }
setStatement = { lval expression }
createStatement = { varName indirectionKind name implementation }
ifStatement = { expression thenPart elsePart }
loopStatement = { implementation }
exitWhenStatement = { expression }
returnTrueStatement = { methodName }
returnFalseStatement = { methodName }
returnValueStatement = { methodName name }
callInternalStatement = { functionReference }
callExternalStatement = { functionReference }
lval =| expression
varName =| name
functionReference =| expression
thenPart =| implementation
elsePart =| implementation
indirectionKind = 'indirect' | 'direct'
methodName =| name
filler =| name
id = { ... }
Defines a class that contains the given fields.
This construct does not explicitly define any methods for the class.
Methods are generated automatically.
id = | ...
Defines a type id to be a union of other types.
id = '...' | '...' | ...
Defines a type to consist of one or more contants (symbols / strings).
id = :string
Defines a type id to be of a foreign type STRING.
[maybe I should have generalized this to id = :foreign?]
id = :map ...
Defines a type id to be a list of some other type.
[maybe I should have named this :list instead of :map]
I find it useful to have 2 stacks for every type
The example type specification in Example Type Specification defines 54 types.
This compiles to 54 type definitions and 54 * 2 = 108 stacks.
Note that there are only 5 possible ways to define a type.
Note that types can be defined as other types. For example
…
methodName =| name
…
name = :string
…
Note that "variable names" are not needed. We simply create a typename for each entity, e.g.
fn (a: int, b : int)
becomes
fn (a, b)
a =| int
b =| int
(this removes syntactic noise from the declarations and pushes implementation details deeper into the hierarchy). In this example, a and b are types (not variables).
Each stack contains enough information to:
There are only 2 types at the bottom:
I find that there are 6 basic operations:
The first 5 operations can be automatically generated for every type (and the corresponding stacks).
The 6th operation (<foreign operation>) is a catch-all for operations that are specific to the solution.
In https://github.com/bmfbp/bmfbp/blob/main/build_process/esa-transpiler/dsl3.pasm (and the other dsl*.pasm files), the stack names are prepended to the operations (followed by two underscores), and the typenames are prefixed with "$" e.g.
$whenDeclarations__EndScope
thus, in the prototype, one will see operations, like:
<type>__NewScope pushes an empty item, of the appropriate type, onto the working stack of the type.
<type>__EndScope pops the given working stack.
<type>__Output moves the top item from the working stack to the output stack for the given type. The working stack is popped (once).
<type>__SetField_<fieldName>_from_<other-type> sets the field fieldName of type to the value of the top of the Output stack of other-type.
This operation checks that the other-type is of the type required by fieldName.
This operation pops (once) the Output stack of other-type.
<type>__AppendFrom_<other-type> appends the value of the top of the Output stack of other-type to the top list on the working stack of type.
This operation checks that type is a list (I've called it :map) and that the element-type of the list matches other-type.
This operation pops (once) the Output stack of other-type.
<type>__??? performs operation "???" on the given type.
This operation has no arguments (other than type), but multiple operations can be declared and called, e.g. counter__reset … counter__increment … counter__increment_by_2 …
[Push these through your favourite pretty-printer]