├── __init__.py
├── test.slisp
├── parser.py
├── test.html.slisp
├── __main__.py
├── README.md
└── slisp.py
/__init__.py:
--------------------------------------------------------------------------------
1 | # hello
2 | #import slisp
3 | #import parser
4 |
--------------------------------------------------------------------------------
/test.slisp:
--------------------------------------------------------------------------------
1 | (func atag "{2}")
2 | (func page (atag {0} "pages" {1}))
3 | (func li "\t
{0}")
4 | (func ul "")
5 | (push "a")
6 | (push "b")
7 | (push "c")
8 | (push "d")
9 |
--------------------------------------------------------------------------------
/parser.py:
--------------------------------------------------------------------------------
1 | from ply.lex import lex
2 | from ply.yacc import yacc
3 |
4 | tokens = ('ID', # var name
5 | 'RPAREN', #)
6 | 'LPAREN', #(
7 | 'STRING', #"
8 | 'COMMENT'
9 | )
10 |
11 | schar = r'a-zA-Z_0-9\$\*\{\}\','
12 | t_STRING = r'"([^"]*?)"'
13 | t_ID = '[%s][%s]*' % (schar, schar)
14 | t_LPAREN = r'\('
15 | t_RPAREN = r'\)'
16 | t_ignore_COMMENT = r'\;.*'
17 | t_ignore = ' \t\n'
18 |
19 | def t_error(t):
20 | print("Illegal character '%s'" % t.value[0])
21 | t.lexer.skip(1)
22 |
23 | lexer = lex()
24 |
25 | def p_lisperal(p):
26 | '''
27 | lisperal : LPAREN lisperal arguments RPAREN
28 | | LPAREN lisperal RPAREN
29 | | ID
30 | | STRING
31 | '''
32 | if len(p) == 5:
33 | p[0] = [p[2], p[3]]
34 | if len(p) == 4:
35 | p[0] = [p[2]]
36 | if len(p) == 2:
37 | p[0] = p[1]
38 |
39 | def p_arguments(p):
40 | 'arguments : argument arguments'
41 | p[0] = [p[1]] + p[2]
42 |
43 | def p_argument(p):
44 | '''
45 | argument : lisperal
46 |
47 | '''
48 | if type(p[1]) == str:
49 | p[0] = p[1].replace('"', '')
50 | if type(p[1]) == list:
51 | p[0] = p[1]
52 |
53 | def p_arguments_empty(p):
54 | 'arguments : '
55 | p[0] = []
56 |
57 | def p_error(p):
58 | pass
59 |
60 | parser = yacc()
61 |
--------------------------------------------------------------------------------
/test.html.slisp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(headerlink stylesheet text/css src/css/index.css)
5 | $(headerlink icon image/jpg src/imgs/icon.jpg)
6 |
7 |
8 | $name
9 |
10 |
11 |
12 |
13 | $(img src/imgs/squid.png "" "120px" "auto")
14 |
15 |
16 | Hello! My name is Leonora and I wanted to make a personal site.
17 |
18 |
19 | I wouldn't expect things to
20 | show up on here regularly, but I wanted a small corner of the internet to aggregate things
21 | I make and like.
22 |
23 |
24 |
25 | Pages :
26 |
27 |
28 | $(list ✧ $(page ./writing.html 'My writing'))
29 | $(list ✧ $(page ./articles.html Other\'s\ writing))
30 | $(list ✧ $(page ./books.html 'Reading list' ))
31 | $(list ✧ $(page https://github.com/0x3444ac53/Not-Important "this site" ' on github'))
32 |
33 |
34 |
35 | Social :
36 |
37 |
38 | $(list ✧ $(atag social "$github" github))
39 | $(list ✧ $(atag social "$instagram" instagram))
40 |
41 |
42 | Acknowledgements
43 |
44 | $<(list ✧ This site was created by forking $(atag acknowledge "https://catgirl.sh" "catgirl.sh") by $(atag acknowledge "https://github.com/turquoise-hexagon" Camille))>$
45 | $<(list ✧ Colorscheme is $scheme by $author)>$
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/__main__.py:
--------------------------------------------------------------------------------
1 | import readline
2 | from os import path
3 | import importlib
4 | import sys
5 | import re
6 | from pprint import pprint
7 | from pathlib import Path
8 | from . import parser
9 | from . import slisp
10 |
11 | def execute(text):
12 | parsed = parser.parser.parse(text)
13 | evald = slisp.evaluate(parsed)
14 | if type(evald) == str and evald != 'None':
15 | return evald
16 | else:
17 | return ''
18 |
19 | def repl():
20 | while True:
21 | try:
22 | repl_input = input("⊃ ")
23 | if repl_input == "reload":
24 | importlib.reload(slisp)
25 | continue
26 | print(execute(repl_input))
27 |
28 | except KeyboardInterrupt:
29 | exit(1)
30 |
31 | def runFile(filePath):
32 | with open(filePath) as f:
33 | for line in f.readlines():
34 | a = execute(line)
35 | print(a, end='')
36 |
37 | def template(file, outputDir='.'):
38 | path = Path(file)
39 | with open(path) as f:
40 | source = f.read()
41 | replacements = (
42 | (i, execute(i[2:-2])) for i in re.findall(r'\$<.*>\$', source)
43 | )
44 | for i in replacements:
45 | source = source.replace(i[0], i[1])
46 | with open(Path(outputDir) / path.stem, 'w') as outfile:
47 | outfile.write(source)
48 |
49 |
50 |
51 | if __name__ == "__main__":
52 | if not not len(files := sys.argv[1:]):
53 | for i in files:
54 | file = Path(i)
55 | if file.suffixes.__len__() > 1 and file.suffix == '.slisp':
56 | template(file, outputDir='docs')
57 | elif file.suffix == '.slisp':
58 | runFile(file)
59 | else:
60 | repl()
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Story
2 |
3 | I was displeased with the way our culture pushes us towards large, complex templating engines that offer little to no arbitrary code execution, too many `%`'s and a frustrating amount of overhead. [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) has loops, ints, native python types, "functions", and (possibly, idk I didn't check) recursion (it probably does). All I want to do is replace one string with another string, and it shouldn't be that hard. [Bash seemed like the perfect tool](https://github.com/turquoise-hexagon/catgirl.sh), until you realise all the hoops one must jump through to have [recursive evaluation in a heredoc](https://github.com/0x3444ac53/Not-Important/commit/cd2a9e3eef115ce5ff0fee3c651e24daa83ada1b#diff-fd3dae0e0ef5235e3f967bcbfa3a3833b22f0fcf8ff0c6c86e1d15c3208d718b). Not to mention the difficulty of trying to [execute a python script](https://github.com/0x3444ac53/Not-Important/blob/cd2a9e3eef115ce5ff0fee3c651e24daa83ada1b/main#L22) without having to create another file.
4 |
5 | I have taken it upon myself to solve this problem for all programmers that fell victim to the computer-science-drop-out-to-finance-bitch pipeline, and created a [Hand Coded Stringy Lisp](https://github.com/0x3444ac53/HCSL).
6 |
7 | # The Last Language you'll ever ~~want~~ need
8 |
9 | All functions evaluate to a lisperal™, there are no variables, only functions that return lisperal™s. Functions are defined using the `func` builtin, which takes two arguments, a id and a lisperal™
10 |
11 | ```lisp
12 | (func foo "{0} {1}")
13 | (foo "hello" "world"); -> "hello world"
14 | (func bar "hello")
15 | (foo (bar) "world"); -> "hello world"
16 | (func baz (foo (bar) "world")); -> "hello world"
17 | ```
18 |
19 | There are no `ints`, `floats`, or any of those mathy things that made you hate linear algebra, just strings. I did add a stack though
20 |
21 | I recognise that this may be a difficult transition for some (and we know all about those), and that edge cases may arise that require something other than strings. So, I have graciously included some built-in functions to aid those that have not yet ascended to a higher plane:
22 |
23 | ```lisp
24 | (exit "1") ; -> exits with exitcode 1
25 | ; not very useful in scripts,
26 | ; but handy in the repl
27 |
28 | (execute "bash code") ; will execute a file on your disk
29 | (debug) ; prints functions definitions
30 | (map foo) ; maps a function 'foo' onto all values on the stack
31 | (push "hello") ; pushes "hello" onto the stack
32 | (pop) ; pops value from stack
33 | (read "filePath") ; will read each line of a file onto the stack
34 | (eval "(bar)") ; evaluates code
35 | (source "filePath") ; will load another slisp file into the current namespace
36 | (concat " ") ; will concatinate all values on the stack with first
37 | ; as a seperater
38 | (concat " " "1" "2") ; will return "1 2" and leave the stack untouched
39 | ```
40 |
41 | # Acknowledgments
42 |
43 | - [catgirl.sh](https://catgirl.sh) by [Camille](https://github.com/turquoise-hexagon) for inspiration
44 | - Samie, who can create a pull request to link to herself if she so desires
45 | - [Crafting Interpreters](https://craftinginterpreters.com/) by [Bob Nystrom](https://github.com/munificent) for introducing me to little languages
46 | - My cat, [Ajax](https://www.instagram.com/p/CvT5ztQgaPs/), for reminding me to stretch
47 | - My faithful companion, [Chester](https://www.instagram.com/p/CusjwW4AKO6/) for 13 wonderful years (R.I.P. 10/07/2023)
48 | - Erik Decker, for answering my calls more often than he should, and because he wanted to be acknowledged
49 | - [evrimoztamur](https://news.ycombinator.com/user?id=evrimoztamur), for [an orange site comment](https://news.ycombinator.com/item?id=37223889) explaining that looping might actually be a good idea
50 |
--------------------------------------------------------------------------------
/slisp.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from pprint import pprint, pformat
3 | from . import parser
4 | from pathlib import Path
5 | import shlex
6 | from shutil import copy
7 | import re
8 |
9 | def define_function(function_def):
10 | functions[function_def[0]] = function_def[1:]
11 | return f"{function_def=}"
12 |
13 | def run_file(args):
14 | script = evaluate(*args)
15 | process = subprocess.run(script,
16 | shell=True,
17 | text=True,
18 | capture_output=True)
19 | return process.stdout
20 |
21 | def print_functions(x):
22 | return f"Stack = {pformat(slisp_stack)}\n\n\nFunctions = {pformat(functions)}\n Thank you SB"
23 |
24 | def process_slisp_file(inFile):
25 | print(f"Processing {inFile}")
26 | with open(inFile) as f:
27 | source = f.read()
28 | try:
29 | replacements = (
30 | (i, evaluate(parser.parser.parse(i[2:-2]))) for i in re.findall(r'\$<.*>\$', source))
31 | for i in replacements:
32 | source = re.sub(re.escape(i[0]), i[1], source, count=1)
33 | except Exception as e:
34 | print(e)
35 | return source
36 |
37 | def template_single(inFile, outFile):
38 | outFile.parents[0].mkdir(parents=True, exist_ok=True)
39 | if inFile.suffix == '.slisp':
40 | processed = process_slisp_file(inFile)
41 | outFile = outFile.with_name(outFile.stem)
42 | with outFile.open('w') as f:
43 | f.write(processed)
44 | returnString = f"SlispFile {inFile} to {outFile}"
45 | else:
46 | try:
47 | copy(inFile, outFile)
48 | return f"{inFile} to {outFile}"
49 | except IsADirectoryError:
50 | returnString = f"skipping {outFile}: is a directory"
51 | return returnString
52 |
53 |
54 | def template(args):
55 | args = [Path(evaluate(i))
56 | for i in args]
57 | inFile, outFile = args
58 | if inFile.is_dir():
59 | return "\n".join([template_single(file, outFile / Path("/".join(file.parts[1:]))) for file in inFile.rglob("**/*")])
60 |
61 |
62 | def slisp_map(args):
63 | global slisp_stack
64 | for i in args:
65 | try:
66 | function_def = functions[i][0]
67 | slisp_stack = list(map(function_def.format, slisp_stack))
68 | except TypeError:
69 | function_def = functions[i]
70 | slisp_stack = list(map(function_def, slisp_stack))
71 | return f"mapped {function_def}"
72 |
73 | def concat(join_on):
74 | global slisp_stack
75 | returnval = ""
76 | join_on = [evaluate(i) if type(i) == list else i for i in join_on]
77 | if len(join_on) > 1:
78 | for i in join_on[1:]:
79 | returnval = returnval + join_on[0] + i
80 | else:
81 | for i in range(len(slisp_stack)):
82 | if i > 0:
83 | returnval = returnval + join_on[0] + slisp_stack.pop()
84 | else:
85 | returnval = returnval + slisp_stack.pop()
86 | return returnval
87 |
88 | def load_file(x):
89 | for file in x:
90 | with open(file) as f:
91 | for i in f.readlines():
92 | slisp_stack.append(i)
93 | return ""
94 |
95 | def source_file(x):
96 | for file in x:
97 | try:
98 | with open(file) as f:
99 | a = [evaluate(parser.parser.parse(i)) for i in f.readlines()]
100 | return "\n".join(a)
101 | except FileNotFoundError as e:
102 | print(e)
103 | return ""
104 |
105 | def shell_quote(x):
106 | return shlex.quote(evaluate(*x))
107 |
108 | def slisp_push(x):
109 | slisp_stack.append(evaluate(*x))
110 | return f"pushed {slisp_stack[-1]}"
111 |
112 | def slisp_stack_dup(x):
113 | slisp_stack.append(slisp_stack[-1])
114 | return f"Duped {slisp_stack=}"
115 |
116 | def evaluate(args):
117 | if not args:
118 | return ""
119 | if type(args) == str:
120 | return args
121 |
122 | try:
123 | func_name, args = args
124 | except ValueError:
125 | func_name = args[0]
126 |
127 | try:
128 | return functions[func_name](args)
129 | except TypeError:
130 | pass
131 | except KeyError:
132 | pass
133 |
134 | try:
135 | function_def = functions[func_name][0]
136 | except TypeError:
137 | function_def = functions[func_name]
138 | except KeyError:
139 | return " ".join([func_name] + args)
140 |
141 | args = [evaluate(i) for i in args]
142 |
143 | if type(function_def) == str:
144 | return function_def.format(*args, dq='"').replace("\\n", "\n")
145 | if type(function_def) == list:
146 | return evaluate(function_def).format(*args)
147 |
148 | slisp_stack = []
149 |
150 | functions = {
151 | "func" : define_function, # function_def
152 | "exit" : lambda x: exit(int(*x)), # repl exit
153 | "source" : source_file,
154 | "execute" : lambda x: run_file(x),
155 | "debug" : print_functions,
156 | "map" : lambda x: slisp_map(x),
157 | "concat" : concat,
158 | "shell_quote" : shell_quote,
159 | "template" : template,
160 | "push" : slisp_push,
161 | "pop" : lambda x: slisp_stack.pop(),
162 | "read" : load_file,
163 | "eval" : lambda x: evaluate(parser.parser.parse(x)),
164 | "dup" : slisp_stack_dup
165 | }
166 |
--------------------------------------------------------------------------------