- What Is a Syntax?
- A Basic Example of Making a Custom Syntax
- Syntax Customization
- Syntax return types
What Is a Syntax?
A syntax is essentially a macro(a single instruction that expands automatically into a set of instructions to perform a particular task).
During compile-time, anywhere the syntax is used will be swapped with the syntax's return value. But rather than getting a return that you use in code (an expression) like a normal method or function(a.k.a. an expression), you get a return that becomes the code (a syntax). The syntax code won't be re-evaluated again until a change is compiled.
A Basic Example of Making a Custom Syntax
use cm.syntax; /** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { exSyn } /** * Call this from inside the syntax. */ public void callFromSyntax() { pln("Call from syntax"); } /** * Example syntax. */ public statement exSyn { pln("Compile Syntax..."); SStatement b = block { callFromSyntax(); pln("I'm in a Statement"); }; pln("Block of statement to run: ", b); return b; }
Compiled output:
Compile Syntax... Block of statement to run: { (callFromSyntax()); pln("I\'m in a Statement"); }
Executed output:
Call from syntax I'm in a Statement
Notice that there are two different outputs here. The compiled output is the output shown after the example code is compiled(Ctrl-Alt-U). Whereas, the code execution output is shown when the code is executed(Ctrl-Alt-P). This demonstrates that the compiler saves the syntax keyword (exSyn
) during compilation into the compiler's memory, so when you execute it, exSyn
will return the statement(SStatement), which is the block of code you want to execute.
The syntax can only be used in the public interface specified at the beginning of the syntax definition. package, and private interfaces are not allowed.
Resulting in essentially the synRun
function be executed as:
/** * Run example syntax. */ public void synRun() { callFromSyntax(); pln("I'm in a Statement"); }
In this example, having exSyn
as a custom syntax seems off, especially without the semicolon ;
. So keep in mind to try making custom syntaxes as readable and intuitive as possible by using syntax customization.
Syntax Customization
We can further customize the custom syntax by adding combinations of signature(s) and argument(s).
Signatures
Other than the keyword of the syntax, signatures are character(s) that form the other parts of the custom syntax to be recognized by the compiler.
use cm.syntax; /** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { exSyn !s.s; exSyn ! s.s ; exSyn ! s.s
; } /** * Example syntax. */ public statement exSyn '!' "s.s" ';' { SStatement b = block { pln("Executing block of code..."); }; return b; }
Output:
Executing block of code... Executing block of code... Executing block of code...
Focusing on the custom syntax creation first, you can observe all the three signatures !
, Y
, and s.s
are placed AFTER the keyword, exSyn
, and must be placed as such.
When the custom syntax is in use, the space(s) and new-line(s) between the keyword and signatures are arbitrary. As long as the keyword itself and each signature(s) match, the compiler will recognize this as one custom syntax. Meaning this would not be recognized as the custom syntax:
exSyn ! s. s;
Arguments
Just like functions, arguments can be passed in for determining the compilation behavior and/or execution behavior of a syntax. The arguments must be a syntax type.
/** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { SynClass classy(); argDemo classy int myInt = 68; argDemo myInt argDemo "a String" } /** * Argument demo syntax. */ public statement argDemo @e=expr { pln("Compile: e = ", e; e.type; e.literalStr); SStatement b = block { pln("Execute: e = ", @e); }; return b; } /** * Example class. */ public class SynClass { public constructor() { } }
Compiled output:
Compile: e = classy, SynClass, null Compile: e = myInt, int, null Compile: e = "a String", str, a String
Executed output:
Execute: e = SynClass(8) Execute: e = 68 Execute: e = a String
Similar to function arguments, while passing in an argument, you have to specify the argument's name and the argument's data type. In this example, @e
is the argument name, and expr
is the argument's syntax type. The argument name must have @
in front. expr
is the shorthand for the SExpr
syntax type which is a subclass of the Syntax
class.
Usage of the argument during compilation is expressed as a syntax type and must be used without @
. Which is why it can use methods in the SExpr
class(type, literalStr).
During execution, the argument is passed into a statement and will transform the argument to it's the actual data type and must be used with @
. 68
turns into an integer, a String
turns to a string, classes will turn to its class object, etc.
Here is a list of syntax types that can be passed in as a syntax argument:
-
actualArg (SActualArg)
The exact same asSExpr
but it can also hold and process keywords of expressions. A sequence member ofSActualArgList
.
-
actualArgList (SActualArgList)
A sequence ofSActualArgs
. Already includes the close brackets and semicolon(args); as signatures.
-
export (SExport)
To indicate the interface(public, package, private) of said member(methods and fields of a class) or definition(class) to return. Does not need to specify the syntax type, but must be placed in between the return type and syntax keyword.
Example:public member @visibility exampleSyntax { return member{}; }
-
expr (SExpr)
Syntactic entity that can be evaluated to determine it's value. Which are primitive data types like integer, boolean, etc. or composite types like sets, maps, classes, etc.
-
formalArgList (SFormalArgList)
Just likeSActualArgs
but can evaluate argument data types asSTypes
and can retrieve it'sSId
.
-
id (SId)
Usually used as the argument to pass a name/identifier to an expression you want to return.
-
src (SSrc)
This can be thought of as the source code itself.
-
statement (SStatement)
Line(s) of executable code.
-
type (SType)
Used to pass in a data type of an expression. Can be primary or composite data types.
There are also special operators you can use in combination with arguments:
-
[ syntax type ]? Optional argument
This gives the syntax the option to not need this argument passed in, any combinations of signatures can also be added into the square brackets./** * Run example syntax. */ public void synRun() { exSyn; exSyn(true); } /** * Example syntax. */ public statement exSyn @expr=['(' expr ')']? ';' { return statement { }; }
-
list[ syntax type, signature ]+ Sequence of arguments
This gives the syntax the ability to append multiple arguments of the same syntax type into one parameter to use in the syntax. A signature must be placed after the syntax type to indicate what signature is used to separate arguments. The+
can also be swapped with a*
, and have no difference./** * Run example syntax. */ public void synRun() { exSyn true with true with 1 } /** * Example syntax. */ public statement exSyn @expr=list[expr, "with"]+ { return statement { }; }
Syntax return types
There are only a few return types a syntax can return, and all of these are also related to the Syntax
class. These are expressions, statements, member, definition, and syntax.
Expression (SExpr class)
An expression is a syntactic entity which we can evaluate data from. Which is basically what usual functions and methods return.
/** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { int syntaxValue = exprDemo; pln(syntaxValue); } /** * Expression return syntax demo. */ public expr exprDemo { return expr { int asdf = 98 }; }
Output:
98
Statement (SStatement class)
A statement is a line of code that expresses some action to be carried out. As a syntax, it means you are returning line(s) of statements that carry out the action.
/** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { statementDemo } /** * Statement return syntax demo. */ public statement statementDemo { return statement { pln("Do statement stuffs."); pln("and then...."); }; }
Output:
Do statement stuffs.
and then....
Member (SMember class)
A member is defined here as a field or method that is part of a class. The interface/export(SExport
) name must be placed between the return type and syntax keyword.
/** * Example empty class. */ public class ExpClass { /** * Blank constructor. */ public constructor() { } /** * Blank method. */ extend public void someMethod() { } } /** * Overriden example class. */ public class OverriddenExpClass extends ExpClass { /** * Use the member syntax. */ public memberDemo } /** * Statement return syntax demo. */ public member @visibility memberDemo { return member { /** * Syntax appended integer field. */ @visibility int syntaxInteger = 68; /** * Syntax overriden method. */ @visibility void someMethod() { pln("syntax has overriden this method"); } }; } /** * Run code. */ { OverriddenExpClass oExpClass(); pln(oExpClass.syntaxInteger); oExpClass.someMethod; }
Output:
68 syntax has overriden this method
Definition (SDefinition class)
The definition of a class itself.
/** * Example class. */ public class ExpClass { /** * Blank constructor. */ public constructor() { } /** * Blank method. */ extend public void someMethod() { } } /** * Use definitionDemo syntax. */ definitionDemo /** * Statement return syntax demo. */ public definition definitionDemo { definition { public class OverriddenExpClass extends ExpClass { /** * Syntax appended integer field. */ public int syntaxInteger = 68; /** * Syntax overriden method. */ public void someMethod() { pln("syntax has overriden this method"); } } }; } /** * Run code. */ { OverriddenExpClass oExpClass(); pln(oExpClass.syntaxInteger); oExpClass.someMethod; }
Output:
68 syntax has overriden this method
Syntax (Syntax class)
This can only be used as a syntax definition's argument. Its return type can be any syntax type or composite data type(ie. sets, class).
/** * Run code. */ { synRun(); } /** * Run example syntax. */ public void synRun() { pln(doubleOrNothing 12 x 2); pln(doubleOrNothing 4.25 x 2); pln(doubleOrNothing); } /** * Double or nothing syntax */ public expr doubleOrNothing @num=multX2 { if (num.type in {int, double}) { return expr { @num }; } return expr { "NOTHING" }; } /** * Double or nothing syntax arugument. */ public syntax SExpr multX2 { @expr=expr 'x' '2' { return expr { @expr*2 }; } empty { return expr { null }; } }
Output:
24
8.5
NOTHING
In this example, we create a special syntax argument called multX2
. If an expression is passed in with the character x
and then 2
, it will multiply the expression by 2 and return the integer as a SExpr
. However, if nothing is passed in, then it'll return an empty SExpr
.
Then in the doubleOrNothing
syntax, if the return's type from multX2
is an integer or a double, it'll return the number, else it'll return a "NOTHING
" string.
Comments
2 comments
Found an error in the article.
"The syntax can also be specified on what interface it can be used, i.e. public, package, private. This is specified at the beginning of the syntax definition."
This part should probably say something like: "The syntax does only support the public interface, i.e. package and private syntaxes are not supported".
Currently if you try to compile "package statement mySyntax { ..." Then you get the following compile error: "only 'public' syntax is supported".
Same if you try to make a private syntax.
Hello Erik,
Thanks for pointing that out, we have edited the article to explain that only the public interface can be used.
Please sign in to leave a comment.