# README
Logi — A language for abstraction
Overview
Logi is a language for abstraction that allows developers to define systems declaratively. It is designed to be simple, flexible, and extensible, making it easy to create complex systems with minimal effort.
Motivation
With the help of Logi, you can create a custom language for your specific system.
Logi is designed to be a language for abstraction that allows developers to define systems declaratively.
Example 1. Define a DSL for a credit rules
credit-rule.lgm
macro creditRule {
kind Syntax
syntax {
creditScore <min int> <max int>
income <min int> <max int>
age <min int> <max int>
}
}
Now let's define a credit rule using the macro:
credit-rule.lg
creditRule Rule1 {
creditScore 500 600
income 20000 30000
age 18 65
}
creditRule Rule2 {
creditScore 600 700
income 30000 40000
age 18 65
}
Now you can easily compile the Logi file and use the definitions in your application.
logi compile -i engine-config.lg
This will parse macro and logi definition file, and will compile it to json, which can be used by your application.
[
{
"macro": "creditRule",
"name": "Rule1",
"data": {
"age": {
"max": 65,
"min": 18
},
"creditScore": {
"max": 600,
"min": 500
},
"income": {
"max": 30000,
"min": 20000
}
}
},
{
"macro": "creditRule",
"name": "Rule2",
"data": {
"age": {
"max": 65,
"min": 18
},
"creditScore": {
"max": 700,
"min": 600
},
"income": {
"max": 40000,
"min": 30000
}
}
}
]
See examples folder for all examples.
Example 2. Define a DSL for a chatbot
macro chatbot {
kind Syntax
syntax {
intent <name Name> {
pattern <pattern string>
response <response string>
}
}
}
Now let's define a chatbot using the macro:
chatbot MyChatbot {
intent Greeting {
pattern "Hello"
response "Hi there!"
}
intent Farewell {
pattern "Goodbye"
response "See you later!"
}
}
Logi language also have Virtual Machine where you can load, read, execute the Logi content.
A sample in golang:
package main
import "github.com/tislib/logi/pkg/vm"
func main() {
var v, err = vm.New()
if err != nil {
panic(err)
}
// load Macro, which will be used to parse actual logi content
_ = v.LoadMacroContent(`
macro conversation {
kind Syntax
syntax {
greeting { expr }
farewell { expr }
}
}`)
// load Logi content, based on given Macro DSL
def, err := v.LoadLogiContent(`
conversation SimpleOp {
greeting { "Hello, " + name }
farewell { "Goodbye, " + name }
}
`)
// execute the loaded Logi content
v.SetLocal("name", "John Doe")
result, err := v.Execute(&def[0], "greeting")
println("Greeting: " + result.(string))
result, _ = v.Execute(&def[0], "farewell")
println("Farewell: " + result.(string))
}
Syntax
In Logi, there are two main elements: macros and definitions. They are defined in separate files, and separately loaded.
Overview
There are different kind of macros in Logi:
- Syntax: Defines the syntax of a new language element.
- Rule: Defines a rule that can be applied to a language element.
- Transform: Defines a transformation that can be applied to a language element.
Syntax Macros
Macro Definition
A Macro
macro {MacroName} {
kind Syntax
types {
{TypeName1} {SyntaxStatement1}
{TypeName2} {SyntaxStatement2}
}
syntax {
{SyntaxStatement1}
{SyntaxStatement2}
}
}
A Definition
{MacroName} {DefinitionName} {
{SyntaxStatement1}
{SyntaxStatement2}
}
In Macro definition, dynamic parts are enclosed in curly braces. For example, {MacroName} is the name of the macro, {TypeName1} is the name of the type, and {SyntaxStatement1} is the syntax statement.
The main thing in macro definition is syntax, which defines how its definitions will be.
Types are optional, it is used to define complex types which can be used in syntax or types
Simple Macro definition
Let's define a simple macro that defines a person with a name and age.
macro person {
kind Syntax
syntax {
name <name string>
age <age int>
}
}
Now, let's define a person definition using the macro:
person John {
name "John"
age 30
}
It will be matched by Logi engine and will be translated into a definition like this:
{
"age": {
"age": 30
},
"name": {
"name": "John"
}
}
Syntax Statements
Syntax statements are the building blocks of a macro. They define the structure of the macro definition.
Each syntax statement is white space separated syntax element(s).
macro {MacroName} {
kind Syntax
syntax {
{SyntaxElement1} {SyntaxElement2} {SyntaxElement3}
{SyntaxElement4} {SyntaxElement5}
}
}
When Logi engine reads a definition, it matches each statement of definition to the macro statement. Statements are unordered, it means that first statement of macro can match with any statement of definition. Statements are also optional, it means that if a statement is not present in definition, it will be ignored.
In other hand, syntax elements are by default required and ordered. It means that first element of macro statement will match with first element of definition statement.
Each statement itself must be written in a single line.
Person Macro:
macro person {
kind Syntax
syntax {
firstName <firstName string> lastName <lastName string>
age <age int>
}
}
Person Definitions:
Correct:
person JohnDoe {
firstName "John" lastName "Doe"
age 30
}
Correct, but unordered:
person JohnDoe {
lastName "Doe" firstName "John"
age 30
}
Incorrect, wrong order of elements:
person JohnDoe {
firstName "John" lastName "Doe"
age 30
}
Incorrect, missing element:
person JohnDoe {
firstName "John"
age 30
}
Correct, missing statement:
person JohnDoe {
firstName "John" lastName "Doe"
}
Incorrect, line separated statement:
person JohnDoe {
firstName "John"
lastName "Doe"
age 30
}
Syntax Elements
There are different type of syntax elements in Logi:
- Keyword: A keyword is an identifier that is used to define a syntax element.
- VariableKeyword: A variable keyword is a keyword that can be used as a variable in the syntax. It is for matching dynamic information in definition
- ParameterList: A parameter list is a list of parameters. It is used to define a list of parameters in a syntax element.
- ArgumentList: An argument list is a list of arguments. It is used to define a list of arguments in a syntax element.
- CodeBlock: A code block is a block of code. It is used to define a block of code in a syntax element.
- ExpressionBlock: An expression block is a block of expression. It is used to define a block of expression in a syntax element.
- AttributeList: An attribute list is a list of attributes. It is used to define a list of attributes in a syntax element.
- TypeReference: A type reference is to include a statement from types section into syntax.
- Combination: A combination is a combination of multiple syntax elements. It is like Or statement
- Structure: A structure is a combination of multiple syntax elements. It is for making nested definition statements.
Keyword
Keywords are used to define syntax structure. They are just plain identifiers and play roles in matching definition
Macro
macro person {
kind Syntax
syntax {
name <name string> as known as <knownAs string>
age <age int> years old
}
}
Definition
person John {
name "John" as known as "Johnny"
age 30 years old
}
it will be translated as:
{
"name": {
"name": "John",
"knownAs": "Johnny"
},
"age": {
"age": 30
}
}
As you see, name
, as
, known
, age
, years
, old
are keywords. They are used to form the syntax structure.
VariableKeyword
Variable keywords are used to match dynamic information in definition.
Variable keywords are defined as <variableName type>
. It is a matching variable in the definition based on a type.
Macro
macro person {
kind Syntax
syntax {
name <name string>
age <age int>
}
}
Definition
person John {
name "John"
age 30
}
In this example, name
and age
are variable keywords. They are used to match dynamic information in the definition.
Based on their types, string is matching "John" and int is matching 30.
List of Variable Keywords types:
Primitive types
string
: It is used to match a string value. Example:name <name string>
will match withname "John"
int
: It is used to match an integer value. Example:age <age int>
will match withage 30
float
: It is used to match a float value. Example:weight <weight float>
will match withweight 70.5
bool
: It is used to match a boolean value. Example:isMarried <isMarried bool>
will match withisMarried true
date
: It is used to match a date value. Example:birthDate <birthDate date>
will match withbirthDate "1990-01-01"
time
: It is used to match a time value. Example:birthTime <birthTime time>
will match withbirthTime "12:00:00"
datetime
: It is used to match a datetime value. Example:birthDateTime <birthDateTime datetime>
will match withbirthDateTime "1990-01-01T12:00:00"
duration
: It is used to match a duration value. Example:workDuration <workDuration duration>
will match withworkDuration "1h30m"
money
: It is used to match a money value. Example:salary <salary money>
will match withsalary "1000.50 USD"
unit
: It is used to match a unit value. Example:weight <weight unit>
will match withweight "70.5 kg"
Special Types
Name type
Name
: It is used to match a name value.
Example: name <name Name>
will match with name John
, It is used to
match a single keyword (without double quotes)
Example:
macro config {
kind Syntax
syntax {
Param <paramName Name> <paramValue string>
}
}
config EngineConfig {
Param LogLevel "info"
Param Port "8080"
}
It will be matched by Logi engine and will be translated into a definition like this:
{
"paramLogLevel": {
"paramName": "LogLevel",
"paramValue": "info"
},
"paramPort": {
"paramName": "Port",
"paramValue": "8080"
}
}
Type type
Type
: It is used to match a type value.
Example: type <typeName Type>
will match with type int
, It is used to match type as value.
Example:
macro entity {
kind Syntax
syntax {
property <fieldName Name> <fieldType Type>
}
}
entity User {
property name string
property age int
}
It will be matched by Logi engine and will be translated into a definition like this:
{
"propertyAge": {
"fieldName": "age",
"fieldType": "int"
},
"propertyName": {
"fieldName": "name",
"fieldType": "string"
}
}
Types defined in types section
Types can be used in variable keywords. It is used to define complex types which can be used in syntax or types.
Example:
macro entity {
kind Syntax
types {
FieldType Name
}
syntax {
field <fieldName FieldType> <fieldValue string>
}
}
entity User {
field name "John"
field age "30"
}
It will be matched by Logi engine and will be translated into a definition like this:
{
"fieldName": "John",
"fieldAge": "30"
}
Another example:
macro entity {
kind Syntax
types {
Property <name Name> <type Type> <size int>
}
syntax {
Prop <prop Property>
}
}
entity User {
Prop name string 50
Prop age int 4
}
It will be matched by Logi engine and will be translated into a definition like this:
{
"propName": {
"name": "name",
"type": "string",
"size": 50
},
"propAge": {
"name": "age",
"type": "int",
"size": 4
}
}
ParameterList
Parameter list is used to define a list of parameters inside parentheses.
Example: Personal (<name String>, <age int>)
will match with Personal ("John", 30)
,
It is like variable keyword, but working in a list and required to have parenthesis.
Macro
macro person {
kind Syntax
syntax {
name <name string>
age <age int>
Personal (<address string>, <phone string>)
}
}
Definition
person John {
name "John"
age 30
Personal ("New York", "1234567890")
}
This will be translated as:
{
"name": "John",
"age": 30,
"Personal": {
"address": "New York",
"phone": "1234567890"
}
}
Parameter list can also be used in types section.
Macro
macro person {
kind Syntax
types {
Address (<city string>, <country string>) at (<street string>, <zip int>)
}
syntax {
name <name string>
age <age int>
Personal <address Address>
}
}
Definition
person John {
name "John"
age 30
Personal Address ("New York", "USA") at ("Wall Street", 10005)
}
This will be translated as:
{
"name": "John",
"age": 30,
"Personal": {
"city": "New York",
"country": "USA",
"street": "Wall Street",
"zip": 10005
}
}
ArgumentList
Argument list is used to define a list of arguments inside parentheses. The difference between parameters and arguments are, parameters are used to define values, arguments are used to define properties.
Example: (...[<args Type<string>>])
will match with (name string, age int)
Macro
macro simpleInterface {
kind Syntax
syntax {
<methodName Name> (...[<args Type<string>>]) <returnType Type>
}
}
Definition
simpleInterface UserService {
createUser (name string, age int) User
}
This will be translated as:
{
"methodName": "createUser",
"args": [
{
"name": "name",
"type": "string"
},
{
"name": "age",
"type": "int"
}
],
"returnType": "User"
}
AttributeList
Attribute list is used to define a list of attributes inside square brackets.
Example: [required boolean]
will match with [required]
Macro
macro person {
kind Syntax
syntax {
<property Name> <type Type> [required boolean, default string]
}
}
Definition
person John {
name string [required]
age int [required, default "30"]
}
This will be translated as:
{
"name": {
"type": "string",
"required": true
},
"age": {
"type": "int",
"required": true,
"default": "30"
}
}
CodeBlock
Code block is used to define a block of code inside curly braces.
Example: { code }
will match with {return "Hello"}
Macro
macro person {
kind Syntax
syntax {
name <name string>
age <age int>
greet { code }
}
}
Definition
person John {
name "John"
age 30
greet {
return "Hello"
}
}
This will be translated as:
code block will not be translated to definition immediately, but you can execute them inside VM.
ExpressionBlock
Expression block is used to define a block of expression inside curly braces.
Example: { expression }
will match with {1 + 2}
Macro
macro person {
kind Syntax
syntax {
name <name string>
age <age int>
greet { expr }
}
}
Definition
person John {
name "John"
age 30
greet {1 + 2}
}
The difference between code block and expression is, inside expression no need to write return
keyword.
TypeReference
Type reference is used to include a statement from types section into syntax.
Example: <TypeName1>
will match TypeName1 from types section
Macro
macro person {
kind Syntax
types {
Name <firstName string> <lastName string>
}
syntax {
name <Name>
age <age int>
}
}
Definition
person JohnDoe {
name "John" "Doe"
age 30
}
This will be translated as:
{
"name": {
"firstName": "John",
"lastName": "Doe"
},
"age": 30
}
TypeReference can be used to simplify complex structures.
Combination
Combination is a combination of multiple syntax elements. It is like Or statement.
Example: (<stringValue string> | <numberValue int>)
will match with "Hello"
or 123
but not true
Macro
macro person {
kind Syntax
syntax {
name (<firstName string> | <firstName Name>) (<lastName string> | <lastName Name>)
age <age int>
}
}
Definition
person JohnDoe {
name "John" "Doe"
age 30
}
Another Definition
person JohnDoe {
name John Doe
age 30
}
Both will be translated as:
{
"name": {
"firstName": "John",
"lastName": "Doe"
},
"age": 30
}
Structure
Structure is used to define nested statements.
Example: {<name string> <age int> Nested2 { <nestedName string> <nestedAge int> }}
will match with
{"John" 30 { "Jane" 25 }}
Macro
macro user {
kind Syntax
syntax {
name <name string>
age <age int>
Auth {
username <username string>
password <password string>
Token {
accessToken <accessToken string>
refreshToken <refreshToken string>
}
}
}
}
Definition
user JohnDoe {
name "John"
age 30
Auth {
username "john.doe"
password "password"
Token {
accessToken "access token"
refreshToken "refresh token"
}
}
}
This will be translated as:
{
"age": {
"age": 30
},
"auth": {
"auth": {
"password": {
"password": "password"
},
"token": {
"token": {
"accessToken": {
"accessToken": "access token"
},
"refreshToken": {
"refreshToken": "refresh token"
}
}
},
"username": {
"username": "john.doe"
}
}
},
"name": {
"name": "John"
}
}
Comments
Comments are used to write notes in the code. They are not executed by the engine.
There are two types of comments in Logi:
- Single line comment: It starts with
//
and ends with the end of the line. - Multi-line comment: It starts with
/*
and ends with*/
.
Example:
// This is a single line comment
/* This is a multi-line comment
It can be used to write multiple lines of comments
*/
Comments can be used in both macros and logi files.