The BASIC Templating Language is written in Pick BASIC for multivalue database environments. It let's you use BASIC code inside templates which removes the need to learn a custom templating language.
A templating language is a language that is used to generate strings. A templating engine will combine a template with data to form a fully rendered page.
The BASIC Templating Language has a requirement to use my implementation of hashmaps as that is how the data is set up and passed into the template.
The BASIC Templating Language is made up of three programs. You can download the files individually and compile and catalog them.
wget https://raw.githubusercontent.com/Krowemoh/basic-template-language/main/RENDER
wget https://raw.githubusercontent.com/Krowemoh/basic-template-language/main/RAW.RENDER
wget https://raw.githubusercontent.com/Krowemoh/basic-template-language/main/EVALUATE.BTL
You can also use my package manager, NPM, to install the 3 routines:
NPM INSTALL BP RENDER
NPM INSTALL BP RAW.RENDER
NPM INSTALL BP EVALUATE.BTL
Only the RENDER program is expected to be used, the RAW.RENDER and EVALUATE.BTL programs are used internally.
SUBROUTINE RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
The RENDER routine takes an environment variable that contains all the data the template needs, it takes the size of the environment, it takes the template to render and finally it returns a fully formed result string.
An example program:
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
CALL MAP.SET(MAT ENV,ENV.SIZE,'NAME','Nivethan')
TEMPLATE = 'Hello, {{ NAME }}!'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
*
END
*
This program sets the variable NAME to my name. The template string references NAME inside the curly braces and so this will get evaluated in the RENDER routine.
The result will be:
Hello, Nivethan!
The TEMPLATE variable should really be a record in the HTML-TEMPLATE-file. However to keep everything simple for the examples I will put the templates directly in the BASIC program.
A template string will contain both strings and BASIC code, BASIC code needs to be embedded within curly braces so that the RENDER routine knows what needs to get evaluate and what is really a string.
The templating language is what get's evaluated by the RENDER routine. A template can contain the following:
Variables
Assign variables
Loops
Conditionals
Print during rendering
Include code from other files
Open files
Read from files
Built in functions: DCOUNT, LEN, OCONV, MOD, DATE, NOT
Call external subroutines to get data (limited to 1 parameter)
As in the example above, the simplest part of the templating language is simply showing a variable.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
CALL MAP.SET(MAT ENV,ENV.SIZE,'NAME','Nivethan')
*
TEMPLATE = 'Hello, {{ NAME }}!'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
Hello, Nivethan!
You can also run BASIC code.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
CALL MAP.SET(MAT ENV,ENV.SIZE,'VAR1',3)
CALL MAP.SET(MAT ENV,ENV.SIZE,'VAR2',4)
*
TEMPLATE = '3 + 4 = {{ VAR1 + VAR2 }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
3 + 4 = 7
You can assign variables inside a template:
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ VAR1 = 3 }}'
TEMPLATE<-1> = '{{ VAR2 = 4 }}'
TEMPLATE<-1> = '3 + 4 = {{ VAR1 + VAR2 }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
3 + 4 = 7
This is handy inside loops to make things more obvious.
You can also loop using the templating language:
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
CALL MAP.SET(MAT ENV,ENV.SIZE,'VAR1',3)
CALL MAP.SET(MAT ENV,ENV.SIZE,'VAR2',4)
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ FOR I = 1 TO 5 }}'
TEMPLATE<-1> = ' I = {{ I }}'
TEMPLATE<-1> = '{{ NEXT I }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
I = 1
I = 2
I = 3
I = 4
I = 5
You can use this to loop through multivalues to display them.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
NUMBERS = 100 : @AM : 200 : @AM : 300
CALL MAP.SET(MAT ENV,ENV.SIZE,'NUMBERS',NUMBERS)
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ FOR CTR = 1 TO DCOUNT(NUMBERS,@AM) }}'
TEMPLATE<-1> = ' CTR = {{ NUMBERS<CTR> }}'
TEMPLATE<-1> = '{{ NEXT CTR }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
I = 100
I = 200
I = 300
A faster loop that can be used is the FOR OF loop. This is not valid BASIC but is an optimization I've added for attribute marked lists.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
NUMBERS = 100 : @AM : 200 : @AM : 300
CALL MAP.SET(MAT ENV,ENV.SIZE,'NUMBERS',NUMBERS)
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ FOR NUM OF NUMBERS}}'
TEMPLATE<-1> = ' I = {{ NUM }}'
TEMPLATE<-1> = '{{ NEXT I }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
Results in:
I = 100
I = 200
I = 300
You can have conditional logic in the template:
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ FOR I = 1 TO 5 }}'
TEMPLATE<-1> = ' {{ IF I % 3 = 0 AND I % 5 = 0 THEN }}'
TEMPLATE<-1> = ' FizzBuzz'
TEMPLATE<-1> = ' {{ END ELSE IF I % 3 = 0 THEN }}'
TEMPLATE<-1> = ' Fizz'
TEMPLATE<-1> = ' {{ END ELSE IF I % 5 = 0 THEN }}'
TEMPLATE<-1> = ' Buzz'
TEMPLATE<-1> = ' {{ END ELSE }}'
TEMPLATE<-1> = ' {{ I }}'
TEMPLATE<-1> = ' {{ END }}'
TEMPLATE<-1> = '{{ NEXT I }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
This program will print FizzBuzz when I is divisible by both 3 and 5. It will print Fizz when I is divisible by just 3 and Buzz when it is divisible by 5. Otherwise it will print the number.
This results in:
1
2
Fizz
4
Buzz
To debug while the rendering process is happening you can use the PRINT statement.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
NUMBERS = 100 : @AM : 200 : @AM : 300
CALL MAP.SET(MAT ENV,ENV.SIZE,'NUMBERS',NUMBERS)
*
TEMPLATE = ''
TEMPLATE<-1> = '{{ FOR NUM OF NUMBERS}}'
TEMPLATE<-1> = ' I = {{ NUM }}'
TEMPLATE<-1> = ' {{ PRINT NUM }}'
TEMPLATE<-1> = '{{ NEXT I }}'
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
The PRINT statement will only print to the terminal, this output won't be part of the resulting string.
The result:
I = 100
I = 200
I = 300
The terminal will look like:
100
200
300
I = 100
I = 200
I = 300
You can include templates:
{{ INCLUDE "HTML-TEMPLATE-FILE" "TEMPLATE-A.HTML" }}
The INCLUDE statement takes a MD entry and a item id.
You can open files directly from within a template.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
TEMPLATE = ''
TEMPLATE<-1> = "{{ FILE.OPENED = 1 }}"
TEMPLATE<-1> = "{{ OPEN '','SITE-FILE' TO SITE.FILE ELSE FILE.OPENED = 0 }}"
TEMPLATE<-1> = "{{ FILE.OPENED }}"
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
This will open a file and if the file does not exist it will set the FILE.OPENED variable.
This results in:
1
You can read a record from within the template:
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
TEMPLATE = ''
TEMPLATE<-1> = "{{ FILE.OPENED = 1 }}"
TEMPLATE<-1> = "{{ OPEN '','SITE-FILE' TO SITE.FILE ELSE FILE.OPENED = 0 }}"
TEMPLATE<-1> = "{{ READ ITEM FROM SITE.FILE,'MAIN' ELSE ITEM = '' }}"
TEMPLATE<-1> = "{{ ITEM<1> }}"
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
This will print out what is in ITEM in the first attribute.
You can use the below functions inside a template:
DCOUNT
LEN
OCONV
MOD
DATE
NOT
It is straightforward to add functions. They need to be added the EVALUATE.BTL routine.
You can call subroutine from within a template. I have only allowed subroutine calls of 1 parameter for now.
*
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
TEMPLATE = "{{ CALL TEST.CALL(RESULT) }}"
TEMPLATE<-1> = "{{ RESULT }}"
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
This will call the TEST.CALL subroutine and print the RESULT.
The above examples all have the template directly in the BASIC program however the way I use it is to have HTML-TEMPLATE-FILE and then I write my templates there.
I then read the template in and call the render program.
EQU ENV.SIZE TO 5000
DIM ENV(ENV.SIZE)
MAT ENV = ''
*
OPEN '','HTML-TEMPLATE-FILE' TO HTML.TEMPLATE.FILE ELSE
PRINT 'Unable to open file'
STOP
END
*
TEMPLATE.ID = 'INDEX.HTML'
READ TEMPLATE FROM HTML.TEMPLATE.FILE,TEMPLATE.ID ELSE TEMPLATE = ''
*
CALL RENDER(MAT ENV,ENV.SIZE,TEMPLATE,RESULT)
*
PRINT RESULT
*
END
*
The BASIC Templating Language let's you compose the various things all together to make relatively complex programs using the templating language.
The OPEN, READ and the external subroutine statements are definitely dangerous and should not be used in templates. Templates should try to minimize the magic and so any data they need should be passed in directly via the environment rather than have the template go out and fetch data.
However these features were fun to implement so here they are.