plugin = {string -> environment}
-- we usesection:
to match on an environment that we want to utilizeenvironment = yalogic list
-- logic can match on existing nodes and transform them- yalogic:
boolean match(YaDefinition def, YaContext context) YaDefinition logic(YaDefinition def, YaContext context) -- change context, output new vars, etc. Things like @yapping/abc.yap we want to match nodes that begin with @ todo - will we also want to add lookups to the state?
- probably needs to be a collection of environments
- for example, in the import plugin
-
represents a plugin, which is a list of defined environments that we may enter
-
an environment is entered by declaring a section
section1: plugin1 = import: MyPlugin; (plugin1.myenvironment: x)
section1: (import: MyPlugin) # myplugin's environments are in the context in precedence (myenvironment: x) # myenvironment is not recognized, out of scope for MyPlugin
import: MyPlugin # myplu section1: {x = myenvironment: asdf} (myenvironment: x)
-
note that a plugin might have different runtime scope based on where and how it's imported.
-
todo - default functionality for map resolution should shadow previously declared items under the same name
-
default functionality for section resolution should merge previous value with current one
what we want: each java class defines a set of environments. Environments "match" against a node to know whether or not they accept it environments may or may not be sticky (e.g. matching with parent node implies matching with child nodes) multiple environments may be on a node at a single time, but closest scope goes first. environment applies a transformation to the node, resulting in another node structure or to some other Object. the result of the node may also produce more than one named Lookup objects, which can get values or apply arguments to the node. the name is included in the variable scope. We also may provide a "merge" function, which deals with name clashes. Essentially given two nodes under the same name (name, A, B) -> M where M is merged. Can be overwriting, can be set union, can be making a new name for the entries. mynode.abc -> lookup abc, abc always treated as a string literal mynode[abc] -> lookup abc. We parse abc as normal with the environment XXX - we should probably just use [abc] as a primitive only lookup has a defined list of matching arguments for a given environment, so it could be possible to use the second form (mymesh.getabove, 3 3/4) vs mymesh.getabove[3 3/4] (glueparts: SPACE, my, var, with, spaces) (glueparts: SPACE LITERAL, my, var, with, spaces) -- reset variables so inner parts are literal translated = (for: i=0, i<mymesh.triangles.length, in: (get: mymesh.triangles, i).x + 1 i = i + 1)
glueparts: SPACE LITERAL (my, var, with, spaces); add = fun: (x, y) (add: ...) myadd = fun: x y (add: (x, y)); myadd2 = fun: w x y (add: (w, myadd2: x y)) fun: x y (add: (x, y)) fun: y (y); myadd2: 1 2 3 4 5 6; myadd = (fun: x y, (add, x, y)) add12 = myadd:12; # or maybe (myadd:, 12) or (myadd, 12) 22 = add12: 10 ;
some environemnts will merge with its parent (like base or math, which merge with the parent)
asdf[3 3/4] list = [a, b, c] map = {a = b, b = c} name = "hello" regex = /hel+o/
(fun:
asdf: asdf: asdf2: :asdf2: ; ; problem: ambiguity. asdf: (asdf: asdf2: asdf2: ;) ; asdf: asdf: (asdf2: asdf2: ;) ; asdf: asdf: asdf2: (asdf2: ;) ;
Yet Another Properties Parser in Gjava
# properties: a list of sections
yapPrimitives: # begin properties
123 # int
123.456 # decimal
1 3/4 # quotient
myName # name
`regex~flags` # regex
"name" # name (literal)
yapConstructs: # (another section)
name1 = 1 # pair
2 = name2 # pair
(item1, 0) # list
{myName1 = name1, myName2 = name2} # map
# properties - we may disambiguate properties within properties
# by surrounding it with ":" sections... ";"
:sectionName:
sectionInfo
anotherSection:
;
combineConstructs:
name1 = name2 = 1 # equivalent to {name1 = {name2 = 1}}
(l, r) = (1, 2)
{a = b, c = d} = (section: 1 2 section: 3 4)
# pair( pair(a, b), pair(c,d), list( properties(section(1 2), section(3 4)) ) )
We may define plugins that will match and transform nodes in the data structure.
The example above defines a Type-3
language as it doesn't require any plugins.
However, we may define Type-[1-3]
plugins that will match and transform existing nodes
in the data structure. This is specified by the library user to determine the initial set of
plugins that we may use. A script may not start using and importing plugins unless if the java
implementation specifies that it can. Here's an example:
import:
@base.yah # setting variables and scopes. @ symbol specifies we're using a file over a java class
boxMesh = @meshes/box.yap
!com.user.TriangleMeshPlugin
vertices:
# creates a list of vertices that start with "neg" and have negated values from boxMesh
:import: @functional.yah @math.yah @strings.yah # import fun:, neg:, and concat:
definitions:
negateAll = fun: {vName = vItem} rest
is: {concat: "neg" vName = neg: vItem} :negateAll: x;
fun: {vName = vItem}
is: {concat: "neg" vName = neg: vItem};
vertices: (negateAll: boxMesh.vertices);.vertices
This is a Type-0
script, where there may be possible turing completeness. Note that we can still parse
this file as Type-3
, which will just result in a structure of nodes.
User plugins are specified with their relevant complexity, Type-0
, Type-1
, etc.
From the user's setup we may specify a series of initial plugins and a target complexity.
If there is any attempt to import any plugin that's at a higher complexity than the parser's target,
then an exception will be thrown and parsing will stop. E.g. a Type-3
script attempting to use a
Type-0
plugin is not allowed, as it may make our script turing complete.
At its base, parsing yapping is a context-sensitive Type-1
grammar. When we refer to a plugin's complexity however,
this refers to the transformations that take place on the node after it is parsed.
For example, a user may make a Type-3
plugin that just matches individual nodes and transforms them into a data
structure usable by the user's java program. Or they could write a Type-2
plugin that matches individual nodes
in a context-free manner. Or, they could write a Type-1
plugin that uses context and variables. Finally, they
could write a Type-0
processor that would recognize nodes as control flow.
Since import itself is a plugin, we have to specify it manually on the java-side. After this is done, import
will
transform the AST among variables in scope
import:
@my/file/path/myimport.yap # we use @ symbol to reference a yap script
@my/file/path/header.yah # we use yah as header files, which only contain other imports or data
!com.user.package.Plugin # used to reference a java resource as a plugin
section:
# all of the above items from import can be referenced from here
# however this does not resolve to an actual
:com.user.package.Plugin.environment1: doSomething; # works - describes a full path to the environment
:Plugin.environment1: nothing ; # need a full path or imported environment
:environment1: doSomething ; # equivalent to first line
:com.user.package.AnotherPlugin: nothing ; # this was not imported, and thus does not use the environment
:import: !com.user.package.AnotherPlugin in: :com.user.package.AnotherPlugin.myPlugin: something; ;
# now the reference works
Syntax
import: ( @[filepath] | ![javaPath] | { [mapValues] }(. [scope] )* )*
Essentially, data can be viewed as a path structure like
com -> user -> plugins -> JavaPlugin1 -> environment1
-> environment2
-> yapping -> JavaPlugin1 -> environment1
-> environment2
Using an import on one of these will expose both the absolute path we imported and the variables after the path we imported.
For example,
import: !com # {user = ... , yapping = ... }
import: !com.user # {plugins = { JavaPlugin1 = {environment1 = .., environment2 = ..} } }
import: user # same as above, as user is defined from the first line
The import acts on any map or environment type. For map types it will assign the string keys defined by the map into the current context referencing the assigned values.
Importing an environment will cause the environment to be applied to all nodes after the import completes. For example,
import: com.user.JavaPlugin1.environment1
environment1 will be applied here
aSection:
environment1 will also be applied here
We may use multiple environments at the same time, this is done by defining a list of environments to be used as a section.
import @yapping/vars.yah # allows us to set variables
!com.user.Plugin1
twoEnvironments = (environment1, environment2)
twoEnvironments:
now we are using both envionments
this is equivalent to
import !com.user.Plugin1
environment1:
:environment2:
now we are using both environments
;
in future iterations we will permit lists in the section head like so
import !com.user.Plugin1
(environment1, environment2) :
now we are using both environments
We can use imports to change the way in which we import. For example, we are allowed to use a regex on a path:
import: # this allows the following line to work
@yapping/regex.yah
!com.`(\w+\.)+`JavaPlugin`\d`.environment1 # form is `[regex]~[flags]`
# {user.plugins.JavaPlugin1.environment1 = .., yapping.JavaPlugin1.environment1 = ..}
Note that the result of a regex import is a map. However here, the keys are paths relative to the first non-regex path of our base. The keys of the map are defined as if they were imported and map to their respective values.
import: @yapping/regex.yah
!com.`(\w+\.)+`JavaPlugin`\d`
# {user.plugins.JavaPlugin1 = environment1, yapping.JavaPlugin1 = environment1}
We can use these regexes to import multiple environments
import
@yapping/vars.yah # set and retrieve variables
@yapping/scope.yah # get scope of items outside of import
@yapping/singleton-list.yah # unboxes (a) -> a
@yapping/regex.yah
environments = (!com.`(\w+\.)+`JavaPlugin`\d`.environment1).vals
environments:
# now using (user.plugins.JavaPlugin1.environment1, yapping.JavaPlugin1.environment1)
Using an import will bring all defined variables of a path into the immediate scope.
Environments imported this way may be referenced using the
environment: args
syntax. One may still reference an environment via its absolute
path, but we are required to import it beforehand.
The scope ends at the end of the properties block, end of the list, or end of a map where the import block is defined.
sectionExample:
:import: !com.user.Plugin
in: :environment: this works;
environment: this works
;
:Plugin: this does nothing ;
:import: !com.user.Plugin
in: :environment: this works;
:Plugin.enviroment: this does nothing;
:com.user.Plugin.environment; this works;
environment: this does nothing
;
listExample:
(Plugin: this does nothing, import: !com.user.Plugin, environment: this works)
(environment: this does nothing)
:environment: this also does nothing;
pairExample:
:import: !com.user.Plugin; = :environment: this works;
x = environment: this does nothing;
:com.user.Plugin.environment: this does nothing; # not imported in this scope
pairExample2:
:import: @yapping/vars.yah
y = :import: !com.user.Plugin;
x = :y.environment: this works;
mapExample: {
x = environment: this does nothing,
import: !com.user.Plugin = (),
x = environment: this does something,
() = import: !com.user.Plugin2,
x = environment2: this also does something
}
listOfPairs: :import: @yapping/vars.yah (
x = environment: this does nothing,
import: !com.user.Plugin = (),
x = environment: this does nothing,
y = import: !com.user.Plugin,
x = y.environment: this works
);
contextExample:
:import: !com.user.Plugin.environment
now everything after this import has the context of environment
;
no longer in environment
import: !com.user.Plugin.environment
now: everthing till end of file is using environment
When using the base import
plugin, environments can only be used and referenced using the section:
syntax, where the
section matches a name to an environment we imported. The environment for import
is matched to an arbitrary section
titled import:
. Note that a plugin contains a set of environments, and if a set of environments is defined on the
given name, all will be applied in order.
An import
, at its base, exposes variables defined at a context level. These can then be used by the yap script using
the section:
syntax to enter an environment. At its base, we don't provide any more functionality by default. However,
this is the only plugin that needs to be specified ahead of time on the java side in order for us to extend the language.
Additional functionality during importing can be provided using the args
, export
, and vars
plugin.
Allows us to export data from a yap
file so it is defined as a variable when referenced in another file.
Here's an example
# mydefinition.yah
import:
@yapping/export.yah
!com.user.Plugin
!com.user.TriangleDataModel
export:
# plugin just references a single environment defined for the plugin
plugin = Plugin.myEnvironment
# triangles is the collection of all environments under TriangleDataModel
triangles = TriangleDataModel
# we can also export data
scale = 1.0
Then it can be referenced later
# mydatamodel.yah
import:
@mydefinition.yah
@vars.yah
plugin:
heres something myEnvironment matches
scale also resolves here
triangles.vertices:
t0 = [0, 0, 0]
t1 = (0, scale, 0)
t2 = (0, scale, scale)
t3 = (scale, 0, 0)
triangles.mesh:
(t0, t1, t2)
(t3, t1, t0)
We can have files export environment functionality to the top level
# mydefinition2.yah
import:
@user/unusedimport.yap
x = @yapping/vars.yah
y = !com.user.SomePlugin
export:
x
y.someEnvironment
!com.user.MyPlugin.myEnvironment
This will export vars.yah
, SomePlugin.someEnvironment
, and MyPlugin.myEnvironment
as top-level
environments that will automatically be applied on import
# myscript.yap
import:
@mydefinitions2.yah
something:
# using three environments from mydefinitions2
One can prevent automatically using environments by providing a name
import:
x = @yapping/vars.yah
aSection:
y = 3 # literally y = 3 pair
(y, y, y) # literally (y, y, y)
x:
y = 3 # sets y to 3
(y, y, y) # equal to (3, 3, 3)
Args allows us to pass data to another yap script, to a plugin on entering an environment, or to a plugin on import.
The args
environment is matched to a top-level section node labeled args:
Here's an example
# myplugin.yah
import: @yapping/getargs.yah
argsmap:
{
FLAG1 = @yapping/regex.yah
FLAG2 = @yapping/vars.yah
}
export:
argsmap.result
This will cause argsmap.result
to evaluate to a different list depending if FLAG1
or FLAG2
is
present as an argument, or none, or both.
# mypassargs.yap
import:
@yapping/setargs.yah # modifies environment so we can pass args to imports
w = :setargs: @myplugin.yah FLAG1;
x = :setargs: @myplugin.yah FLAG2 FLAG1;
@myplugin.yah ABC FLAG1 # setargs is not required but necessary if we want to set the import to a variable
z = @myplugin.yah # unless if we have no args afterwards
Here we add additional imports from the header based on the args passed in. We can also use these args to define variables, pass around data, or even get data we can reference in java.
Allows us to define and reference variables. Additional semantics to come...
Allows us to access scopes of variables and values. Additional semantics to come...
Regexes have the form
`regex~flags`
The regex can be used for pattern matching (in tandem with the args
plugin) or for multiple scope
resolutions (in tandem with the import
or scope
).
Todo - add functionality for calling and matching against regexes. Have a plugin specifically
for pattern matching (possibly in conditions
file)
Base is our go-to entrypoint for most files, as it defines vars
and scope
. It also optionally
exports export
, regex
, singleton-list
, setargs
, getargs
.
It may also optionally define strings
, math
, and conditions
, which contain basic non-turing complete functionality.
We permit defining properties in the form :section1: section2: ;
When we encounter the first colon :
, we enter a new properties.
We then find section1:
to define the first section.
Then we find section2:
to define the final section.
Finally, we find ;
, which ends the current properties.
Note that the first :
or last ;
is not required. For example,
x = section: abc;
will create properties with one section
, then end the properties.
The following example defines ways we can define a section without ambiguity.
outerSection:
x = section: abc;
x = :section: abc; # equivalent to above
# without ';' rest of file would be in this section.
(section: abc,
:section: abc,
:section: abc;,
section: abc;) # these four are equivalent
:section: abc; # both : and ; are required to disambiguate from outerSection
:section: abc; = x # both : and ; are required for this to be a key, or else it would resolve to below
:section: abc = x # NOT the same as above - resolves to :section: (abc = x) ;
{section: abc = x,
:section: abc = x,
section: abc; = x,
:section: abc; = x} # these four are equivalent, as we look for the '=' sign to split the map into pairs before
# parsing its key/value
{section: abc = def; # we do at least require ; here, or else we'd match against the first '=' sign.
= section: abc = def}