EsoML

==What is EsoML==
EsoML stands for Esoteric Markup Language and it is an esoteric language that allows you to write code
as if you were writing Vue.js rendering functions, but with the feel of assembly. It's a language
that lets you create awesome websites that feel like they're from the 2000s, without any attributes to elements and
styling. Raw HTML rendered using JavaScript under the hood.
The language is based on a similar concept as modern compiled languages such as Vue.js with it's openBlock etc.
functions, but reversed. It also resembles the feel of true assembly, although it has some big differences.
Adding attributes to elements and styling them is a TODO, it's not yet implemented.
==A simple EsoML Truth Machine==
.unsafe_mode ihatemyselfcuzirequireanargumentineverysection
.strings en_US
Let 1 be translated to The truth machine in EsoML.
Let 2 be translated to Enter either 0 to print 0 once or 1 to print 1 indefinitely (2 is for unknown input): .
Let 3 be translated to Output: .
Let 4 be translated to Input: .
Let 5 be translated to 0.
Let 6 be translated to 1.
Let 7 be translated to .
.rom en_US
Remember that 1 will always be 0.
Remember that 2 will always be 1.
Remember that 3 will always be 2.
.code init
push 150t Load ''
push 102c Load 2
.render main
call truthMachine
.render truthMachine
cont h1
text 78t
econ
cont span
text 90t
cont input
hear input parseInput
econ
econ
cont
text 114t
text 0s
econ
cont
text 102t
call tick
econ
.render tick
copy Here starts the IF stack[0] == 0
push 78c
comp
ifis
call setStrZero
endi
copy Here starts the IF stack[0] == 1
push 90c
comp
ifis
call addStrOne
rend
endi
text 1s
.render setStrZero
push 126t
swap 2 0
pops
.render addStrOne
swap 0 1
push 138t
madd
swap 0 1
.code parseInput
push 150t Load ""
swap 2 0 Set it to the output text on the stack
pops Remove the old output text from the stack
read Check whether "0" was the input
push 126t
comp
copy
read Check whether "1" was the input
push 138t
comp
copy
swap 0 2 Check whether the input was neither "1" or "0"
madd
push 90c
msub
ifis If <unknown> was the input
push 102c Load 2
swap 3 0 Set it to the state on the stack
pops Remove the old state from the stack
endi
ifis If "1" was the input
push 90c Load 1
swap 2 0 Set it to the state on the stack
pops Remove the old state from the stack
endi
ifis If "0" was the input
push 78c Load 0
swap 1 0 Set it to the state on the stack
pops Remove the old state from the stack
endi
rend Update/rerender
==EsoML syntax guide==
This is a simple guide on how to use EsoML.
Line numbers are counted starting with 1. There are no explicit comments. However, you can implicitly comment
instructions. That is because very instruction has the syntax/format as described
below <type> <argument1> <argument2>...<argumentN>, you can use the arguments unused by the instruction to pass in
pseudo-comments (which will be treated as unused arguments for the instruction by the compiler). An example
is the following render section:
.render main
cont h1 This will create a h1 container tag/element
text 0s Show the text from the top of the stack
econ Don't forget to close it
===Sections===
Sections are started with a so-called section header: .<section> <argument>
====Strings section====
Format: .strings <locale>
This is the section where your localized strings will be stored. The <locale> parameter is the locale you're defining
strings for.
You write strings there in the following format: Let <id> be translated to <text>.. The <id> parameter is
the ID of the <text> (not the final number/key you reference in code) base-8 encoded for better readability.
See references to values for more info. The <text> parameter is the text you
want to store.
Example:
.strings en_US
Let 1 be translated to EsoML string example.
====ROM section====
Format: .rom <locale>
This is the section where your localized read-only numbers will be stored. The <locale> parameter is the locale you're
defining numbers for.
You write numbers there in the following format: Remember that <id> will always be <number>.. The <id> parameter is
the ID of the <number> (not the final number/key you reference in code) base-8 encoded for better readability.
See references to values for more info. The <number> parameter is base-11
encoded to make
your code cleaner.
Example:
.rom en_US
Remember that 1 will always be 3.
====Code and render sections====
Format:
- Code - .code <label>
- Render - .render <label>
These are the sections where your main code will be stored. The <label> parameter is the label you call to control the
execution flow. The difference between .code <label> and .render <label> is the fact that .code <label> is used to
handle events and .code main is used to initialize the stack, for example, whereas the .render <label> section is
used as a component that can simply be reused and that will be able to output elements (thus it can use
the text, cont and other render-only instructions.)
There are two special sections with special purposes:
.code init Runs once before the first render. Use it for example to initialize the stack.
.render main Runs once by default, however it is recommended to use the rend instruction to re-render.
Example:
.render main
cont h1 Creates a container (div by default)
text 78t Adds text to the content (equivalent to innerText)
econ Closes the container
===Instructions===
All instructions are exactly four characters long for better readability.
Format: <type> <argument1> <argument2>...<argumentN>
====Start container - cont====
The cont <type?|div> instruction is used to start/open a container.
====End container - econ====
The econ instruction is used to end/close a container.
====Create element - elem====
The elem <type> instruction is used to create an element without the ability to set its contents.
It's an equivalent of doing this:
cont <type>
econ
====Render text - text====
The text <text> instruction is used to inject/add/render text. Can be thought of as elem.innerText += <text>.
====Render raw HTML - show====
The show <html> instruction is used to inject/add/render raw HTML. Can be thought of as elem.innerHTML += <html>.
Note that only the first HTML element the raw HTML results to when parsed is added/rendered.
====Execute/render a different section - call====
The call instruction is used to either execute another code section if called from a code section, or inject
another render section if called from a render section (can be thought of as using a custom UI component). The
executed section is run from its beginning to its end, there's no conditional return instruction. The only thing you can
do is conditionally execute instructions (and potentially perform nested calls) from within the ifis-endi statement.
====Schedule a re-render - rend====
The rend instruction is used to schedule a re-render. Can be used in both code and render sections, as it is
essential to create a Truth Machine. Scheduled re-renders are executed every 100ms, though that can be changed
in lib.js --> RERENDER_TIMEOUT
====Add event listener - hear====
The hear <event> <callback> instruction is used to add an event listener to listen to any kind of event. This is the
bond of code and render sections, because it can only be used in a render section, but the <callback> must be
a code section.
====Push a value to the stack - push====
The push <value> instruction is used to push a value to the top of the stack.
====Duplicate the top value on top of the stack - copy====
The copy instruction is used to duplicate the value at the top of the stack. Technically, it is an equivalent of
doing push 0s.
====Pop the top value from the stack - pops====
The pops instruction is used to pop a value from the stack. The value is simply discarded.
====Swap two values on the stack - swap====
The swap <offA?|0> <offB?|1> instruction is used to swap the desired values on the stack. By default, it swaps the two
values at the top of the stack. To allow for complex programs, you can overwrite the offsets to your desired values.
====Compare the top two values on the stack - comp====
The comp instruction is used to compare the top two values on the stack. The compared values are deleted. If they are
equal (JavaScript comparison ===) then 1 is pushed onto the stack, otherwise 0 is pushed. The process/calculation
is the following:
- Pop A from the top of the stack
- Pop B from the top of the stack
- Push A === B ? 1 : 0 to the top of the stack
====Read contents of an element - read====
The read instruction is used to read the contents of an element. If the element is an input, it will read its value.
Otherwise, it will fall back to reading innerHTML. Which element are the contents read from? In the code section, it
is the element the event listener was bound to using the hear instruction. In the render section, the element is the
current container you're writing the instruction in. If the target element cannot be determined, an error is thrown at
runtime. See the console for the currentTarget variable to see the current target element (when in debug mode).
====Maths - ADD - madd====
The madd instruction is used to add two numbers together. The process/calculation is the following:
- Pop A from the top of the stack
- Pop B from the top of the stack
- Push A + B to the top of the stack
====Maths - SUB - msub====
The msub instruction is used to subtract two numbers from each other. The process/calculation is the following:
- Pop A from the top of the stack
- Pop B from the top of the stack
- Push A - B to the top of the stack
====Maths - MUL - mmul====
The mmul instruction is used to multiply two numbers. The process/calculation is the following:
- Pop A from the top of the stack
- Pop B from the top of the stack
- Push A * B to the top of the stack
====Maths - DIV - mdiv====
The mdiv instruction is used to integer divide two numbers. The process/calculation is the following:
- Pop A from the top of the stack
- Pop B from the top of the stack
- Push A // B to the top of the stack (// is integer division, can be represented as JavaScript Math.floor(A / B))
====Start if statement - ifis====
The ifis instruction is used to start an if block. The top value is popped from the stack and if, and only if, the
value is exactly equal to 1 as an integer, the contents of the if statement are executed. You can compare two values
with the comp instruction before using the comparison result in an if statement.
====End if statement - endi====
The endi instruction is used to end an if block.
===References to values and id vs key===
There are three types/ways of referencing a value:
1. <key>t - reference to a key of a text in the strings section, for example 69t is a ref to a text in the strings with its key being 69
2. <key>c - reference to a key of a constant in the rom section, for example 69c is a ref to a constant in the rom with its key being 69
3. <off>s - reference to an offset from the end of the stack, for example 1s is a ref to the second item on the stack counted from the top of the stack
The keys are derived from ids, using a simple mathematical formula. It depends on the offset from the start of the
section (line number offset), so you can create the same offsets for multiple localizations. The source code for the
function can be found in the esoml.lexer file, around line 65 of the file, it's a method of a class EsoMLLexer
defined as the following:
def convertIDToKey(id_str: str, line: int) -> int:
 id_: int = int(id_str, 8)
 line %= len(id_str)
 line %= 3
 if line == 0:
 return ((id_ * 3) << 2) + 0x42
 elif line == 1:
 return ((id_ ^ 1337) << 3) - 5
 elif line == 2:
 return ((id_ // 7) % 0x69) + 666
 else:
 raise NotImplementedError("Huh? Math is failing?")
To describe the algorithm, we can use simpler notation:
ID = parseInt(ID_STR, base=8)
OP_ID = LINE_NUM % LEN_ID % 3
RESULT = ((ID * 3) << 2) + 0x42 for OP_ID == 0
 ((ID ^ 1337) << 3) - 5 for OP_ID == 1
 ((ID // 7) % 0x69) + 666 for OP_ID == 2
where:
- LINE_NUM is the line number offset starting with 0 from the section header (lineNumber - section.startsAtLine)
- ID_STR is the string version of the id provided in the source code, encoded in base 8 for convenience
- ID_LEN is the length of the string version of ID_STR in characters
Absolutely do not look at the compiler output in the Python console because it would help you to figure out which id at
which line corresponds to which key.
==Examples==
You can find example code together with some comments in the examples/ folder in the interpreter's GitHub repo below.
==Interpreter==
* Interpreter of EsoML on GitHub: https://github.com/kubikaugustyn/EsoML
Category:Markup_Languages
Category:Declarative_paradigm
Category:2024
Category:Stack-based
Category:Unusable for programming
Category:Turing complete
Category:Graphical_Output
Category:Implemented
Category:Low-level
Category:Flat-nested
Category: Languages