Skip to content

Instantly share code, notes, and snippets.

@wware
Created April 15, 2022 18:21
Show Gist options
  • Save wware/5decf92362cb57cfa0a4c114285dfdd4 to your computer and use it in GitHub Desktop.
Save wware/5decf92362cb57cfa0a4c114285dfdd4 to your computer and use it in GitHub Desktop.
A bit of fun with literate programming using Markdown

Literate Programming hacks

Let's try some literate programming. From Don Knuth's original idea, thru Norman Ramsey's noweb reformulation, by way of Jonathan Aquino's Python version, which I tweaked to accept Markdown input and added a few new features.

$ ./noweb.py -R 'outer function' lp_hacks.md | python
Hello world

inner function

def _inner(x):
    print(x)

That function above will be called by this function below.

outer function

#*inner function*#

def _outer(x):
    _inner(x)

_outer("Hello world")

Hack prolog

People have put a lot of work into Prolog. There must be something to it. I know Ross King used it in his Robot scientist project. It seems to be an ideal computational environment for exercises in first-order logic.

To exercise some of the stuff below, you can run the "root" chunk below by installing SWI Prolog and then typing

./noweb.py -R root lp_hacks.md | bash

root

./noweb.py -R 'try some prolog' lp_hacks.md > foo.pl
# enumerate solutions non-interactively
swipl -f foo.pl -g "forall((Goal=sibling(X,Y),call(Goal)),(write(Goal),nl))." -t halt.

try some prolog

mother_child(trude, sally).

father_child(tom, sally).
father_child(tom, erica).
father_child(mike, tom).

sibling(X, Y) :- parent_child(Z, X), parent_child(Z, Y).
parent_child(X, Y) :- father_child(X, Y).
parent_child(X, Y) :- mother_child(X, Y).
#! /usr/bin/env python3
"""
noweb.py
By Jonathan Aquino ([email protected])
This program extracts code from a literate programming document in "noweb"
format. It was generated from noweb.py.txt, itself a literate programming
document. For more information, including the original source code and
documentation, see
http://jonaquino.blogspot.com/2010/04/nowebpy-or-worlds-first-executable-blog.html
Subsequent modifications by Will Ware
"""
import os
import sys
import re
import argparse
parser = argparse.ArgumentParser(
prog=os.path.basename(__file__),
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__
)
parser.add_argument(
'--ref', '-R',
help='the root chunk to be extracted',
)
parser.add_argument(
'--out', '-o',
help='specify an output file',
)
parser.add_argument(
'--exectuable', '-x',
help='if an output file was specified, chmod +x that file',
)
parser.add_argument(
'filename', metavar='filename', nargs='+',
help='the source file(s) from which to extract',
)
opts = parser.parse_args()
if opts.out:
outfile = open(opts.out, 'w')
else:
outfile = sys.stdout
chunks = {}
REFERENCE = "^(\s*)#\*(.*)\*#"
CHUNK_NAME = "^\*(.*)\*$"
CHUNK_DELIMITER = "^```(\w*)\s*"
def read_source(lines):
chunkName = pendingChunkName = None
for line in lines:
line = line.rstrip()
match = re.match(CHUNK_NAME, line)
if match:
chunkName = None
pendingChunkName = match.group(1)
continue
match = re.match(CHUNK_DELIMITER, line)
if match:
if pendingChunkName and not chunkName:
chunkName = pendingChunkName
pendingChunkName = None
if chunkName not in chunks:
chunks[chunkName] = []
else:
chunkName = pendingChunkName = None
continue
if chunkName:
chunks[chunkName].append(line)
def expand(chunk, indent):
chunkLines = chunks[chunk]
expandedChunkLines = []
for line in chunkLines:
match = re.match(REFERENCE, line)
if match:
more_indent = match.group(1)
expandedChunkLines.extend(expand(
match.group(2),
indent + more_indent
))
else:
expandedChunkLines.append(indent + line)
return expandedChunkLines
MISSING_REF = """Please provide a reference, e.g. '-R foobar'.
Here is what's available in your source file(s):"""
for filename in opts.filename:
read_source(open(filename).readlines())
keys = list(chunks.keys())
keys.sort()
if not keys:
print("No references found in source file(s)")
sys.exit(1)
if not opts.ref:
print(MISSING_REF)
for k in keys:
print(f' -R"{k}"')
sys.exit(1)
for line in expand(opts.ref, ""):
print(line, file=outfile)
if opts.out and opts.executable:
os.system("chmod ugo+x " + opts.out)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment