Timers

From Esolang
Jump to navigation Jump to search

Created by User:Rphii in 2021. Timers is an Esolang that is stack based.

Idea

The idea is that Timers relies on time. No need to worry about incrementing anything, because time will do it for you!

The Rules

Comments

There are three types of ways to make comments.

  1. The easiest one, but the riskiest one is that any invalid code is treated as a comment.
  2. The second slightly less riskier version involves the unique trait of named scopes. Those types of comments are quite situational. You can treat a string as a comment, denoted by surrounded apostrophes (those guys: '). We're skipping quite a bit, maybe come back later if you understood scopes.
    • One example is this: 'this is a comment'. Beware, that if nothing except whitespace is between the end of the "comment" and a {} automatically assigns that comment as that scopes' name.
    • A more failsafe version might be this: 'this is a comment{}', and the arguably failsafest version: 'this is a comment{(~)}, in case we accidentally enter the scope by "calling the comment" and being stuck in an infinite loop, if no timers get destroyed.
    • However, one caviat of this commenting technique is that you can't have two equal comments within the same scope. That changes however within a function of time, where you can have multiple equal comments and you can even let the {(~)} part away, since they will still be executed and we try to search for a matching named scope to enter. And if there is none, we simply continue.
    • Also, you have to adhere to the fact that there are escape sequences and that they have to be correct.
  3. The safest way to comment out things is by taking advantage of the fact that it doesn't make sense to destroy one timer two times in succession, we're talking of the operation ~. So, by using two ~~ right next to each other, anything on that line afterwards is treated as a comment. Note that unlike in the programming language C, you can't escape the end of line with a \, so you have to effectively mark each line as a comment, if you wish to do so.

Time and related

  • At program startup there is one timer whose value is initialized to 0.
  • Every timer counts one up, until a corresponding time function is found, where they will pause until the code inside finished executing.
  • Once a timer overflows it resets to 0.
  • Every scope is treated like a seperate program with "private" timers.
  • Technical note: The interval between counting is undefined. Ideally it approaches t=0, because then the interpreter/compiler is optimized.

Stack

  • At startup the stack is empty.
  • If nothing is mentioned about an empty stack, the following rules apply.
  • When performing any command that pops and uses two values of the stack but the stack only has one value, the missing value is assumed to be 0.
  • Aside from the exception stated above, if the stack is empty, any command that has to do something with it gets ignored.
  • Every scope uses the same stack.

Termination

The program or scope stops as soon as the last timer within that scope was destroyed.

Functions and Scopes

Most of the code is executed when called through a function of time.

Command Description
t(c) Function of time, where t gets replaced with an expression or term and c are command(s). See sections Expressions, Terms and Commands.
s{f} Defines a scope, where s gets replaced with a name and f are functions of time.

Flow of Execution

  • The program starts executing in the main scope.
  • The main scope is any code outside of defined scopes.
  • Only the timers of the current scope are able to count.
  • Once a scope is terminated, flow of execution is returned to its parent scope.

Operations

The following operations can be put inside any function of time t.

Character Description
~ Destroy caller's timer after the function finished executing.
^ Push caller's time.
$ Pop top of stack and discard.
\ Swap top stack values. Ignored if size of stack less than 2.
: Duplicate top stack value.
; Push size of stack.
. Pop top of stack and output as integer.
, Pop top of stack and output as character. (Supports Unicode)
" Print a newline.
# Pop top value, n, and push n-th stack value (starting from the bottom). If the stack is smaller than the given index, ignore.
` Pop two values, a (top) and n, overwrite n-th stack value with a (starting from the bottom). If the stack is smaller than the given index, ignore.
? Pop top of stack and insert character into code, if it's a valid Unicode character. If the stack is empty, insert an _ instead. Using multiple of ? will automatically concenate them with whatever is before/after them. The resulting instruction is either one of the command list or a function call. And finally, if it results in an invalid instruction, ignore.
& Get an integer from the user and push it. If a string is entered it will still try to convert numbers but it will also convert each character to its Unicode codepoint and push that instead
@ Get a string from the user and push it. (latter characters in the string are pushed first, so that the beginning lies on the top)
+ Addition: Pop two values a and b, then push the result of a+b.
- Subtraction: Pop two values a (top) and b, then push the result of b-a.
* Multiplication: Pop two values a and b, then push the result of a*b.
/ Integer division: Pop two values a (top) and b, then push the result of b/a, rounded down.
% Modulo: Pop two values a (top) and b, then push the remainder of the integer division of b/a.
> Greater than: Pop two values a (top) and b, then push 1 if a>b, otherwise 0.
< Less than: Pop two values a (top) and b, then push 1 if a<b, otherwise 0.
= Equals: Pop two values a and b, then push 1 if a=b, otherwise 0.
! Logical NOT: Pop a value. If the value is 0 push 1; otherwise, push 0.
| If : Pop a value and exit function if not empty and not equal to 0 (continue if 0 or empty).
s Execute scope, where s gets replaced with a scope name (see scopes).
{f} Execute scope, where f gets replaced with function(s) of time. Similar to s above, but inlined.
[t] Initiate a new timer after the function finished executing, starting from t, where t is a number (see new).

New

The following combinations can be but inside [] to create new timers. New timers start searching for matching time functions starting on the next one (prevents this: ([0]~)-(.~) from turning into an endless loop).

.. Description
[.] Maximum value any timer can have.
[,] 0 if stack is empty.
[?] Value at top of stack (not modifying).
[!] Value below top of stack (not modifying).
[$] Last destroyed timer's value (scope independent).
[&] By how much any timer was incremented previously, if > 0.
[%] Insert previous values of new.
[^] Value at index by top of stack. (starting from the bottom)
[@] Value at index by below top of stack. (starting from the bottom)
[\] Top of stack if not 0.
[/] Maximum value any timer can have if stack is not empty.
[>] Number of timers if that is > stack length, else empty.
[<] Stack length if that is > number of timers, else empty.
[=] Number of timers running in the current scope.
["] Sum of all timers running in the current scope.
[:] Scope name.
[;] Scope depth (main scope = 0, increment when going deeper).
[`] Count of how often timers have been incremented already.
['A string'] Match : All of the characters, keeps the order. Supports C-style escape sequences and Unicode.
[1234567890] Number : Any number you can think of. Supports C-style hex (0x) or octal (0) prefixes

Divide by Zero

Dividing (or modulo) by zero brings some consequences with it:

  • Applies to: Current scope only
  • It destroys all (to the scope known) spacetime, therefore code gets terminated immediately and all timers (within that scope) blend together.
  • Realities are shuffled through and the sole remaining timer takes a random value out of all the possibilities within that scope, except for the max. count any timer can have.
  • TL;DR Randomize. (The non-dramatic version)

Modifiers

.. Description
| Concatenation
- Range
+ Linear Sequence
* Exponential Sequence
# Times

Concatenation

Takes anything on either side and concatenates it with the thing before.

Range

  • Takes anything on either side, even a string.
  • If one of either of the side is a string, it will create a range for all characters, e.g. 'abc'-'' is synonymous with 'a'-'b'-'c'-''. Empty strings are removed and the whole construct is reduced to whatever is left, so the previous range is actually going to be 'a'-'b'-'c'. Single characters are converted to their Unicode codepoint.
  • If there is a chain of values, e.g. 1-3-5-7 it will automatically assume that the programmer wants this instead 1-3|3-5|5-7 A range of two values creates each value once, so 1-5 is actually 1|2|3|4|5.
  • If the left side is not specified, default to 0.
  • If the right side is not specified, default to the max. count any timer can have.

Linear Sequence

  • Takes anything on either side, even a string.
  • If one of either of the side is a string, it will create a linear sequence for all characters, e.g. 'abc'+'' is synonymous with 'a'+'b'+'c'+''. Empty strings are removed and the whole construct is reduced to whatever is left, so the previous range is actually going to be 'a'+'b'+'c'. Single characters are converted to their Unicode codepoint.
  • If there is a chain of values, e.g. 1+3+5+7 it will automatically assume that the programmer wants this instead 1+3|3+5|5+7.
  • A linear sequence is where is the i-th value of the sequence, the left and the right value.
  • Optionally, one can specify an inclusive upper limit with a range modifier, e.g. 1+3-10 is synonymous to 1|4|7|10.
  • Optionally, one can specify a times modifier, e.g. 1+3#10 is synonymous to 1|4|7|10|13|16|19|22|25|28. (which will consider up to 10 values, as specified by that 10)
  • You can't use both modifiers at the same time.
  • If the left side is not specified, default to 0.
  • If the right side is not specified, default to left item.

Exponential Sequence

  • Takes anything on either side, even a string.
  • If one of either of the side is a string, it will create an exponential sequence for all characters, e.g. 'abc'*'' is synonymous with 'a'*'b'*'c'*''. Empty strings are removed and the whole construct is reduced to whatever is left, so the previous range is actually going to be 'a'*'b'*'c'. Single characters are converted to their Unicode codepoint.
  • If there is a chain of values, e.g. 2*3*5*7 it will automatically assume that the programmer wants this instead 2*3|2*5|2*7.
  • A linear sequence is where is the i-th value of the sequence, the left and the right value.
  • Optionally, one can specify an inclusive upper limit with a range modifier, e.g. 1*3-100 is synonymous to 1|8|27|64|125|216|343|512|729|1000.
  • Optionally, one can specify a times modifier, e.g. 1*3#10 is synonymous to 1|4|7|10|13|16|19|22|25|28. (which will consider up to 10 values, as specified by that 10)
  • You can't use both modifiers at the same time.
  • If the left side is not specified, default to 0.
  • If the right side is not specified, default to left item.

Times

  • Takes anything on either side, even a string.
  • If one of either of the side is a string, it will create an exponential sequence for all characters, e.g. 'abc'#'' is synonymous with 'a'#'b'#'c'#''. Empty strings are removed and the whole construct is reduced to whatever is left, so the previous range is actually going to be 'a'#'b'#'c'. Single characters are converted to their Unicode codepoint.
  • If there is a chain of values, e.g. 2#3#5#7 it will automatically assume that the programmer wants this instead 2#3|2#5|2#7.
  • In this context, times is referring to multiplication of two values.
  • If the left side is not specified, default to 0.
  • If the right side is not specified, default to left item.

Time Functions

  • Use any of the special operations used within the section new in combination with modifiers. However, the numbers shall not be placed in square brackets, but before a pair of round brackets, e.g. HERE().
  • Within the () is the actual function body, where commands (operations) are executed character for character.
  • You can have up to one line break (and any amount of horizontal whitespace) that a number is associated to a function of time. Otherwise, if no number can be associated to a function of time, default to 0.
  • All matching time function are executed if any timer with it's current value matches any of it's number(s).

The only difference in numbers to the new operation

  • New operation: [1 2] is different from [1|2]. If we have no valid modifiers between any two numbers, including literals or whitespace, the newest timer will be the rightmost. If we correctly concatenate them, the newest timer will be the leftmost. This adheres to the rules of creating matches of strings: ['A string']
  • time functions: 1 2() is equivalent to the function of time 2(). where the 1 gets discarded. If you really want to either hit 1 or 2, you have to concatenate those in this case: 1|2().

Scopes

  • Scopes are recognized by curly brackets: {}
  • There are two types of scopes:
    • Inline scopes, that are always nameless and shall be within a function of time, e.g. (IN HERE).
    • Named scopes, that are always named and shall be outside function of times, e.g. OVER HERE ()

Inline scopes

  • The quickest way to easily change scopes is by using inline scopes, as those are automatically entered.
  • Entering any kind of scope starts a new timer, with the value of 0.
  • Exiting any kind of scope happens when there is no timer left.

Named scopes

  • The easiest way to reuse similar scopes is by naming scopes, as those are entered as soon as they are called by their respective name from within a function of time.
  • Entering any kind of scope starts a new timer, with the value of 0.
  • Exiting any kind of scope happens when there is no timer left.
  • A function name can be any literal or string, e.g. name{}, 'scope name'{} (note the string + space), and even 😁😀{} are valid scope names. It can also start with numbers and whatnot.
  • It doesn't matter hot much whitespace is between the name and the {}, the name is always attached to the next scope.
  • You can have nested scopes. For each nesting, only unique scope names are allowed. This means that you can have equal scope names, if they reside on different nestings.

Calling Scopes

  • There are several ways to call a scope: the literal scope name, their string representation or by using the ? operation.
  • Use whitespace to seperate between different scope callings, because each of those mentioned possibilities always concatenates strings, so (scope' 'name) is actually ('scope name').
  • If there is a scope called name{} (or 'name'{}) you can call it by either (name) or ('name').
  • If using the ? operation, we try to first and foremost call a function by creating a string. We do that by popping values off the stack until a valid string is made. There may be an invalid string, if the top of stack is ' but there is no second one, so beware, because in this case the stack will be reverted back to how it was. After either all ? are used up and replaced by stack values, or a next ? is a valid operation, we have our callstring.
  • If we call a scope but can't find a matching scope, we simply continue, without entering or exiting any scope. Any stack modification is kept.
  • When calling equal named scopes from different nesting levels, the nearest one is used, e.g. the one deepest within. The opposite isn't possible; A scope from the outside can't call scopes that reside within another scope.

Documenting (repeat on comments)

Text outside of time functions can be used as documentation. (comments) There are however two rules one should keep in mind:

  1. The brackets (){} can't be used as documentation (you can still use []<>)
  2. Since the syntax is pretty forgiving, we need to distinguish between comments and scopes (again, the brackets...). For that, see the scenario below.

Scenario

Suppose you want to end your comment about the function of time 0 with the number 1 (could be any term). First, this is how you shouldn't do it:

This is comment nr. 1
(code)

Since it can just as well be interpreted as a comment with a function of time 1:

This is comment nr.
1(code)

This can be handy in some situations, but not in this one, so here's two workarounds:

This is comment nr. 1

(code)

Notice how there is an extra line between the comment and the function of time 0. This indicates that those two items (comment and code) are indeed seperated.

This is comment nr. 1
0(code)

In the example above we explicitly said that the function is a function of time 0.

Rules

Flow of Execution

  • The program starts executing in the main scope.
  • The main scope is any code outside of defined scopes.
  • Only the timers of the current scope are able to count.
  • Once a scope is terminated, flow of execution is returned to its parent scope.

Timers

  • Timers can be explained with a virtual stack model.
  • All timers in the active scope count when there is no matching time function.
  • Batches of timers get pushed in reversed order. Eg: [0|1] => pushing 1, then 0
  • Upon creation of timers, they get pushed to a virtual stack. Eg: [0][1] => pushing 0, then 1
  • Newly pushed timers (top) take priority over older timers.
  • A timer calls each unique time function at most once, when counting is halted.
  • Time functions are searched and executed forwards from the end of the calling function and wrap around back to the beginning (itself).

Assume we have the simple example below. (WXYZ are line numbers used to explain)

V: 1([2])
W: 1(~) 
X: 1(...)  this one never gets called
Y: 0([2|1])
Z: 2(...)  ... should represent some code inside
  1. Initial timer A has the value 0. We search for a time function and the first one we find is on line Y.
  2. The function on line Y gets called once. The function creates two new timers: B starting from 1 and C starting from 2.
  3. Timer C takes priority over timer B, because C is newer. (priorities, values: C=2 > B=1 > A=0)
  4. As soon as the function finished executing, we search for matching time function or increment the timer's values.
  5. We find a matching time function for timer C on line Z. Its function gets executed once.
  6. Next up we find a matching time function for timer B on line V. It creates a new timer D who starts at 2 and exits the function. (priorities, values: D=2 > C=2 > B=1 > A=0)
  7. Timer D has one matching time function on line Z, which gets executed once.
  8. After that, timer B has another matching time function on line W. Its function gets executed once. After executing the code inside it, it destroys that timer. (priorities, values: D=2 > C=2 > A=0)
  9. Now there is no remaining and matching time function, increment all timers (by one). (priorities, values: D=3 > C=3 > A=1)
  10. Find a matching time function, starting from the highest priority.
  11. Timer D and C have no matching time functions, skip.
  12. Timer A has a matching time function on line V. Execute it once: Create a new timer D, starting from 2. (priorities, values: D=2 > C=3 > B=2 > A=1)
  13. Timer D has a matching time function on line Z. Execute it once.
  14. Timer D now has no matching time functions.
  15. Timer A has another matching time function on line W. After executing the code inside, timer A gets destroyed. (priorities, values: D=2 > C=3 > B=2)
  16. Now, there is no remaining and matching time function, increment all timers (by one, until any timer (in this example timer D) hits a time function (in this example line Y)
  17. The whole process begins again
  18. This example would not result in a good output, because we exponentially create timers.

TL;DR: Be careful.

Examples

Truth machine

,(&[?]~)
?(^.^|"~)

Cat

?(,[?]~;|")(&)

Calculator

Takes a value, an operation (+, -, * or /), a second value and outputs the result.

(&&&\?."~)

Hello World

The naive approach would probably look something like this, but it would only print \n ,!HWdelor:

'Hello, World!\n'(^,).(~)

The proper version looks more like this:

(['Hello, World!\n']~)-(^,~)

Fibonacci

Prints the Fibonacci sequence in order.

1(^^{.(~)?(:."+^\)}).(~)

ASCII Table

This program prints the ascii table. From ASCII with values 33 to 126.

33-126(^,).("~)

Interpreter

External resources