Package: psll
psll-langMacro-driven metalanguage which compiles toPyramid Scheme. Readthe paper!🔧 Usage💡 Examples🧬 Additional featuresBracket expansionStringsDef keywordCommand expansionUnderscore keyword🧚🏻‍♀️ Code optimisationGreedy optimisationConsiderate optimisation🔧 UsageDownload and install psll:cd~
gitclonehttps://github.com/MarcinKonowalczyk/psll-lang.gitcdpsll-lang
pipinstall.Thenpsllcommand should be available in your command line. Runpsll --helpto lean more.You can also skip the installation and run the python module as a script withpython -m psll ...without any installation.Only thecompilecommand is implemented at the moment, so you'll have to run the resulting pyramid scheme by yourself with, for example,ruby ./Pyramid-Scheme/pyra.rb ....For example, to compile and run the bubble-sort example:psll-vcompile./examples/bubble_sort.psll-o&&\ruby./Pyramid-Scheme/pyra.rb./examples/bubble_sort.pyraYou can also run the pyramid schem straight frompsllcli. For that to work, make surerubyis in the path.psllrun./examples/bubble_sort.pyraThere is also a convenience command to compile a psll program in a temp directory and run it:psll-vcompile-and-run./examples/bubble_sort.psllHere it it specified with a-voption to also count the number of💡 ExamplesThe following is an example lisp-like pyramid scheme which counts the number of input arguments it gets, and then prints it out to the command line. The syntax supports//comments and newlines, as well as (almost) random additions of whitespace.For the purposed of the markdown README, C# highlighting seems to look fine. There is a vscode extension in thepsll-langfolder which provides syntax highlighting for both psll and Pyramid Scheme.// Make nil by asking for the 999'th input argument(setnil(arg999))// Count the number of input arguments - n(setnargin0)(docond((setnargin(+nargin1))(setcond(!(=(argnargin)nil)))))(setnargin(-nargin1))(out"nargin: "nargin)// PrintIt can be compiled and run as follows:python-mpsll./examples/nargin_counter.psll-o-f
ruby./Pyramid-Scheme/pyra.rb./exmaples/nargin_counter.pyra43524or with thecompile-and-runpsllcompile-and-run./examples/nargin_counter.psll43524The output isnargin: 5🧬 Additional featuresPsll implements a few bits of syntactic sugar, to make writing complicated pyramid schemes easier.Bracket expansionA bracket with multiple subtrees will get automatically split into multiple size-2 subtrees which will, in turn, get prepended with the empty string, as described above. Hence, the following psll code:((out1)(out2)(out3)(out4)(out5))will get interpreted as:(((// First pair(out1)(out2))(// Second pair(out3)(out4)))// One left(out5))The elements are taken from the list pairwise, and put into sub-lists recursively, until the root list has length of 2 or less. Because of order of subtree evaluation in pyramid scheme, this preserves the execution order. Note that arbitrary indentation, and mid-line comments are allowed.This feature is useful, for example, for keeping tidy the body of a for-loop or an if-statement:(seta0)// Flip-flop(setN10)(setj0)// N of iteration and loop counter(loop(!(=jN))(// Do some work...(outj(chr32))// Print j and space(outa(chr10))// Print a and newline(seta(!a))// Flip a(setj(+j1))))StringsIn pyramid scheme, the strings have to be built manually. The string hello is:(seta(+(+(+(+(chr72)(chr101))(chr108))(chr108))(chr111)))Psll expands a string literal into such construct, so its enough to write:(seta"Hello")Only"can be used to create strings.Def keywordPsll implementsdefas a special keyword. The syntax fordefis:(def x (...)), wherexcan be any string (exceptdef) and(...)is any bracket. From that point onwards, each occurrence ofxor(x)is replaced by the bracket(...). Thedefstatement itself gets replaced by an empty pyramid (hence evaluates to 0). For example:(deff(seta(!a)))// This becomes ()(f)// This becomes (set a (! a))defcan, therefore be used akin to a function definition:(seta0)(defincr(seta(+a1)))// Increment a(incr)// This now becomes (set a (+ a 1))(out"a: "a)(out(chr10))Once defined, it is possible to redefinedef's in terms of themselves:(defincr(// Redefine 'incr' as itself + printing(incr)(out"<incr> a: "a)(out(chr10))// Newline))(incr)// This now does the original incr + printDefs are activewithin the scope in which they are declared. Hence:(deff(incr))// 'f' is an alias for 'incr'(f)// This now behaves as 'incr'((out"entering scope"(chr10))(deff(out"Vikings approaching!"(chr10)))// Redefine f within the scope(fff)// Do 'f' 3 times(out"leaving scope"(chr10)))(out"a: "a)(out(chr10))// But 'a' remains unchanged(f)// 'f' works the same way as before the scope(incr)// and 'incr' also works the same wayThe code above printsVikings approaching!three times, as opposed to incrementingathree times, btu then goes back to incrementing after the scope.The following is, for example, apostfiximplementation of the modulo function:(defmod(loop(<=>(<=>ab)-1)(seta(-ab))))// Set a to mod(a,b)(seta11)(setb7)(mod)Command expansionTheoutcommand of Pyramid Scheme allows for output of, at most, 2 variables. To output more, one needs to chain mutiple suchoutstatements. In psll an out command with more than two inputs gets automatically expanded into such chain, such that:(outab)(outcd)(oute)can be written simply as:(outabcde)The former might, however be preferable in certain contexts since it allows for comments on each part of theout.Asimilarexpansion will is also implemented for binary operators+,*as well as-,/,^,=and<=>, such that:(+1234)// This(out(+(+(+12)3)4)newline)// Becomes thisAddition and subtraction are commutative over the set of all possible inputs, and hence the exact order of operations does not matter. (This is not quite true. String multiplication overloads concatenation and that's not commutative). For a non-commutative operation, e.g. subtraction, the expansion order does matter. Hence:(-1234)// This(-(-(-12)3)4)// Does indeed expand into thisbut,(1234-)// This(-1(-2(-34)))// Expands to this insteadFor the binary operations listed above, they can be specified at the end of the bracket to perform a right-associative expansion.For the sake of compatibility with non-expanded brackets, the following two are also allowed, and identical:(-12)(12-)(and, of course, the same for other binary operations, even the commutative ones)Underscore keywordThe underscore_can be used to explicitly specify an empty slot where a pyramid could be. It is not particularly useful from the user point of view (maybe except for fine-tuning the position of the pyramids for code golf), but it is very helpful for the compiler. All the leaves are eventually terminated with_, and string expansion used the_keyword to help pack the code a bit better.🧚🏻‍♀️ Code optimisationPsll compiler allows for some code optimisation. Optimising the code for speed would be, let's be honest with ourselves, a bit silly at this point. Psll optimisation attempts, therefore, to minimise number of bytes in the source code, such that the result can be used incode golf challenges.Greedy optimisationAttempt to package each pair of root nodes in the abstract syntax tree. Insert an empty pyramid in the very first place which is beneficial (hencegreedy). If all such places have been exhausted, try all teh possible insertions of a single pyramid.This optimisation technique tends to result in tall pyramid scheme. It is very fast, and produces intermediate-quality results.Considerate optimisationConsider all the possible places to either insert a single pyramid, or package two adjacent pyramids up to certain depth (10). Choose the most beneficial.This optimisation technique tends to result in wide pyramid scheme. It is slower than the greedy optimisation, but very often results in a smaller pyramid scheme.