From 9514e9ddae3b46812e3827c9719647916bfbe20c Mon Sep 17 00:00:00 2001 From: Reid 'arrdem' McKenzie Date: Fri, 20 Aug 2021 23:16:59 -0600 Subject: [PATCH] Initial deeply busted Lilith state for LangJam --- projects/lilith/BUILD | 9 +++ projects/lilith/README.md | 81 ++++++++++++++++++++ projects/lilith/setup.py | 20 +++++ projects/lilith/src/lilith/designdoc.lil | 47 ++++++++++++ projects/lilith/src/python/lilith/parser.py | 84 +++++++++++++++++++++ projects/lilith/test/python/conftest.py | 3 + projects/lilith/test/python/test_parser.py | 26 +++++++ 7 files changed, 270 insertions(+) create mode 100644 projects/lilith/BUILD create mode 100644 projects/lilith/README.md create mode 100644 projects/lilith/setup.py create mode 100644 projects/lilith/src/lilith/designdoc.lil create mode 100644 projects/lilith/src/python/lilith/parser.py create mode 100644 projects/lilith/test/python/conftest.py create mode 100644 projects/lilith/test/python/test_parser.py diff --git a/projects/lilith/BUILD b/projects/lilith/BUILD new file mode 100644 index 0000000..58e3348 --- /dev/null +++ b/projects/lilith/BUILD @@ -0,0 +1,9 @@ +py_project( + name = "lilith", + lib_deps = [ + + ], + test_deps = [ + py_requirement("hypothesis"), + ] +) diff --git a/projects/lilith/README.md b/projects/lilith/README.md new file mode 100644 index 0000000..01d4295 --- /dev/null +++ b/projects/lilith/README.md @@ -0,0 +1,81 @@ +# Lilith + +> The theme of the jam is "first-class comments". + +```c +// foo bar baz +``` + +``` clojure +; text that the parser throws away + +(def ) +(def ^{:doc "foo bar baz"} ) +``` + +``` python +"""modules have __doc__ as a property defined by the FIRST heredoc-string in the file""" + +def foo(a, b): + """Adds a and b. + + >>> foo(1, 2) + 3 + """ + + return a + b + +``` + +``` clojure +(defn foo [a b] + "Adds a and b + + >>> (foo 1 2) + => 3 + " + (+ a b)) +``` + +- Object code +- .... Other media? + +``` clojure +(defn foo [a b] + (!doc "") + (!doctest "") + (!test "") + + (!impl + (+ a b))) +``` + +``` clojure +(defn foo [a b] + "--- + doc: | + Adds a and b + + --- + doctest: + - in: (foo 1 2) + out: 3 + " + ) +``` + +``` org +#+TITLE: foo +* my bullet + #+BEGIN_SRC clojure + (defn foo [a b] (+ a b)) + #+END_SRC +``` + +`org-babel` `org-babel-tagle` + +``` clojure +(defn foo [] ..) +``` + +#################################################################################################### diff --git a/projects/lilith/setup.py b/projects/lilith/setup.py new file mode 100644 index 0000000..7af5daa --- /dev/null +++ b/projects/lilith/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup + +setup( + name="arrdem.lilith", + # Package metadata + version="0.0.0", + license="MIT", + # Package setup + package_dir={"": "src/python"}, + packages=[ + "lilith", + ], + install_requires=[ + "lark", + ], + test_requires=[ + "pytest", + "hypothesis", + ], +) diff --git a/projects/lilith/src/lilith/designdoc.lil b/projects/lilith/src/lilith/designdoc.lil new file mode 100644 index 0000000..e5d18fc --- /dev/null +++ b/projects/lilith/src/lilith/designdoc.lil @@ -0,0 +1,47 @@ +!defscope[designdoc] +!scope[designdoc] + +!def[pitch] +!frag[lang: md] + +# The Lilith Pitch + +Code is more than .. just code for the compiler. +We write lots of artifacts related to our software + - Design document + - Formatted API listing + - Formatted book which is MORE than just the API / source listing + - A spec? OpenAPI example + - Doctests? / inline test cases + +Theory - most of your "program" isn't code. +idea: the parser is going to IGNORE stuff by default. + +Let's build an M-expression syntax with support for multiple co-equal languages and fragments/scopes with a meta-syntax allowing for references between them and consuming fragment-notation-defined blocks? (FIXME: better word?) + +!def[sytnax] +!frag[lang: md] + +\![] bang-brackets is a meta-syntactic directive used both by the lil tangler and the lil object language. + +Programs consist of many fragments, which can reference each-other and from which executable framents are woven. + +Fragments define interpretations and scopes. + +Interpretations control sub-languages, the bang-bracket notation is more of a meta-language than a target language. +Interpretation is defined by the `lang:` key of a bang-bracket. + +FIXME: how to define more langs? + +FIXME: how do scopes get defined? +\!defscope[name: <>] + +FIXME: we need bare words, we need strings + +FIXME: We need the object language + +!def[main] +!frag[lang: lil] +; is importing a bang-operation? +import[tagle] +print[tangle[pitch, syntax]] diff --git a/projects/lilith/src/python/lilith/parser.py b/projects/lilith/src/python/lilith/parser.py new file mode 100644 index 0000000..f183c3d --- /dev/null +++ b/projects/lilith/src/python/lilith/parser.py @@ -0,0 +1,84 @@ +""" +Variously poor parsing for Lilith. +""" + +import typing as t +import re + +import lark + +class Block(t.NamedTuple): + tag: str + args: list + kwargs: list + body_lines: list + + @property + def body(self): + return "\n".join(self.body_lines) + + +class TreeToTuples(lark.Transformer): + def kwargs(self, args): + d = {} + key, val = args[0:2] + if len(args) == 3: + d.update(args[2]) + d[key.value] = val.value + return d + + def args(self, args): + _args = [args[0].value] + if len(args) == 2: + _args = _args + args[1] + return _args + + def header(self, parse_args): + print("Header", parse_args) + tag = None + args = None + kwargs = None + body = [] + + iargs = iter(parse_args[1]) + tag = parse_args[0] + v = next(iargs, None) + if isinstance(v, list): + args = v + v = next(iargs, None) + if isinstance(v, dict): + kwargs = v + + return Block(tag, args, kwargs, body) + + +block_grammar = lark.Lark(""" +%import common.WORD +%import common.WS +%ignore WS +?start: header + +args: WORD ("," args)? +kwargs: WORD ":" WORD ("," kwargs)? +arguments: args "," kwargs | args | kwargs +header: "!" WORD "[" arguments? "]" +""", + parser='lalr', + transformer=TreeToTuples()) + + + +def shotgun_parse(buff: str) -> t.List[object]: + def _parse(): + block = None + for line in buff.splitlines(): + if line.startswith("!"): + if block: + yield block + block = [line] + else: + block.append(line) + if block: + yield block + + return list(_parse()) diff --git a/projects/lilith/test/python/conftest.py b/projects/lilith/test/python/conftest.py new file mode 100644 index 0000000..58a7168 --- /dev/null +++ b/projects/lilith/test/python/conftest.py @@ -0,0 +1,3 @@ +""" +Pytest fixtures. +""" diff --git a/projects/lilith/test/python/test_parser.py b/projects/lilith/test/python/test_parser.py new file mode 100644 index 0000000..f6bec6f --- /dev/null +++ b/projects/lilith/test/python/test_parser.py @@ -0,0 +1,26 @@ +"""tests covering the Lilith parser.""" + +from lilith.parser import block_grammar, Block + +import pytest + +@pytest.mark.parametrize('example, result', [ + ('!def[syntax]', + Block('def', ['syntax'], None, [])), + ('!frag[lang: md]', + Block('frag', None, {'lang': 'md'}, [])), + ('!frag[foo, lang: md]', + Block('frag', ['foo'], {'lang': 'md'}, [])), +]) +def test_parse_header(example, result): + assert block_grammar.parse(example) == result + + +( + """!def[designdoc] +!frag[lang: md] +# Designdoc + +A design document""", + None +)