UW27LKXM2BJ77FQLTY4WPKDSSWI2RFNFRJ7CB4U3TS7KYVIV72LQC
/^@shebang/ {
awk_executable = "/usr/bin/original-awk"
awk_switches = " -f"
trivial_program = " 'BEGIN { print \"hi\" }' "
no_output = " >/dev/null 2>/dev/null"
if (0 != system(awk_executable trivial_program no_output)) {
awk_executable = "/usr/bin/awk"
# now, what do we need to hand it to get no bells and
# whistles? with GNU awk, we need -c, but One True Awk does
# not recognize this switch.
if(0 == system(awk_executable " -c " trivial_program no_output)) {
awk_switches = " -cf"
} else {
# leave awk_switches alone
}
}
print "#!" awk_executable awk_switches
next
}
/^@include/ {
fn = $2
print "# --- " fn
while((getline < fn) > 0)
print "# ---"
print ""
next
}
{ print }
#!/bin/sh
# SPDX-License-Identifier: BSD-2-Clause
# look for @@ below; these are the variables that will be filled in to
# make sane_lisp
LOG_LEVEL=1
# vvv https://github.com/dnmfarrell/tap.sh/
TAP_TEST_COUNT=0
TAP_FAIL_COUNT=0
tap_pass() {
TAP_TEST_COUNT=$((TAP_TEST_COUNT + 1))
echo "ok $TAP_TEST_COUNT $1"
}
tap_fail() {
TAP_TEST_COUNT=$((TAP_TEST_COUNT + 1))
TAP_FAIL_COUNT=$((TAP_FAIL_COUNT + 1))
echo "not ok $TAP_TEST_COUNT $1"
}
tap_end() {
num_tests="$1"
[ -z "$num_tests" ] && num_tests="$TAP_TEST_COUNT"
# echo "1..$num_tests"
[ "$num_tests" = "$TAP_TEST_COUNT" ] || exit 1
exit $((TAP_FAIL_COUNT > 0)) # C semantics
}
tap_ok() {
if [ "$1" -eq 0 ]; then
tap_pass "$2"
else
tap_fail "$2"
fi
}
tap_cmp() {
if [ "$1" = "$2" ]; then
tap_pass "$3"
else
tap_fail "$3 - expected '$2' but got '$1'"
fi
}
# ^^^
lisp_with_string () {
echo "$@" | original-awk -v PROMPT= -v LOG_LEVEL=$LOG_LEVEL -f glotawk
# importantly, awk is the last thing in this pipeline so we can see
# its exitcode
}
lisp_eval_should_be () {
[ $LOG_LEVEL -ge 2 ] && echo "INF ---- test $(($TAP_TEST_COUNT + 1)) ---------"
local output="$(lisp_with_string "$1")"
local exitcode=$?
if [ "$exitcode" -ne 0 ] ; then
tap_fail "$3 - nonzero exit code"
else
tap_cmp "$output" "$2" "$3"
fi
}
if [ "$#" -gt 0 ]; then
while [ "$#" -gt 0 ]; do
if [ "$1" = "-v" ]; then
if [ $LOG_LEVEL -lt 3 ]; then
LOG_LEVEL=$(( $LOG_LEVEL + 1 ))
fi
fi
shift
done
fi
TEST_COUNT=0
echo '1..16'
lisp_eval_should_be '(quote 5)' '5' 'basic quote'
lisp_eval_should_be '5' '5' 'numbers are literal'
lisp_eval_should_be '((lambda (x) 3) 5)' '3' 'lambda evaluation'
lisp_eval_should_be '(label ((foo 3)) foo)' '3' 'label evaluation'
lisp_eval_should_be '(cond ((false 5) (true 3)))' '3' 'cond evaluation'
lisp_eval_should_be '(atom 5)' 'true' 'atom: number'
lisp_eval_should_be '(atom (quote "foo"))' 'true' 'atom: string'
lisp_eval_should_be '(atom (quote (1 2 3)))' 'false' 'atom: list'
lisp_eval_should_be '(atom (quote (1 . 2)))' 'false' 'atom: pair'
lisp_eval_should_be '(atom (quote socrates))' 'true' 'atom: symbol'
lisp_eval_should_be '(cons (quote foo) (quote bar))' '(foo . bar)' 'cons: pair'
lisp_eval_should_be '(cons 1 (cons 2 (cons 3 nil)))' '(1 2 3)' 'cons: list'
lisp_eval_should_be '(car (quote (foo bar)))' 'foo' 'car'
lisp_eval_should_be '(cdr (quote (foo bar)))' '(bar)' 'cdr'
lisp_eval_should_be '(eq "foo" "bar")' 'false' 'eq: unequal strings'
lisp_eval_should_be '(eq "foo" "foo")' 'false' 'eq: strings are not interned (non-normative)'
tap_end
# SPDX-License-Identifier: BSD-2-Clause
function _print_with_no_ors(x, sors) {
sors=ORS
ORS=""
print x
ORS=sors
fflush()
}
function _prompt() {
_print_with_no_ors(PROMPT)
}
function rep(s) {
_print(_eval(read_str(s), _TOPLEVEL))
}
BEGIN {
if(PROMPT == 0) PROMPT = "user> "
_TOPLEVEL = _nil()
_prompt()
}
{ rep($0); _prompt() }
# SPDX-License-Identifier: BSD-2-Clause
function tokenize_into(input, ta, chars_left, tal, mp) {
chars_done = 0
chars_left = length(input)
tal = length(ta)
while(chars_left > 0) {
if(match(input, /^[ ,]+/)) {
} else if(match(input, /^~@/)) {
ta[++tal] = substr(input, 1, RLENGTH)
} else if(match(input, /^[\[\]{}()'`!^@]/)) { # special single char
ta[++tal] = substr(input, 1, RLENGTH)
} else if(match(input, /^"(\\.|[^\\"])*"?/)) { # double-quoted string
ta[++tal] = substr(input, 1, RLENGTH)
} else if(match(input, /^;.*/)) { # comment
ta[++tal] = substr(input, 1, RLENGTH)
} else if(match(input, /^[^ \[\]{}('"`,;)]+/)) { # non-special chars
ta[++tal] = substr(input, 1, RLENGTH)
} else {
logg_err("tokz", "unrecognized input at char " \
chars_done ": " input)
exit 1
}
if(RSTART != 0) {
# all patterns are anchored to ^ so RSTART is always 1
input = substr(input, 1+RLENGTH)
chars_left -= RLENGTH
chars_done += RLENGTH
} else {
logg_err("tokz", "at char " chars_done ", token not matched: " input)
exit 1
}
logg_dbg("tokz", " -> " tal " tokens; " chars_left " chars_left; input: " input)
}
}
function read_str(s, i, ta, tal) {
delete ta[1] # make sure ta is an array for gawk -c
tokenize_into(s, ta)
tal = length(ta)
i[1] = 1 # make i an array so we can pass it by reference
return read_form(i, ta, tal)
}
function read_form(i,ta,tal) {
if(match(ta[i[1]], /^\(/)) {
logg_dbg("read_form", "( at token " i[1])
i[1] += 1
return read_list(i,ta,tal)
} else {
return read_atom(i,ta,tal)
}
}
function read_list(i, ta, tal, prevtail, head) {
head = _nil()
logg_dbg("read_list", "at beginning, i is " i[1] "; token is " ta[i[1]])
for(; (i[1]<=tal) && (ta[i[1]] !~ /^[).]/); i[1]++) {
logg_dbg("rd_l", "in loop, i is " i[1] "; token is " ta[i[1]])
head = _cons(read_form(i, ta, tal), head)
}
logg_dbg("read_list", "after loop, i[1] is " i[1] "; token is " ta[i[1]] "; head is " head)
prevtail = head
head = _nreverse(head)
logg_dbg("read_list", "heyo")
if(ta[i[1]] == ".") {
i[1] += 1
_set_cdr(prevtail, read_form(i, ta, tal))
i[1] += 1
} else if(ta[i[1]] ~ /^\)/) { # properly terminated
logg_dbg("read_list", "after _nreverse, head is " head)
return head
} else {
logg_err("read_list", "unbalanced parentheses at token: " ta[i[1]-1])
return _nil()
}
}
function read_atom(i, ta, tal, this) {
# examples, separated by spaces: 3 3.14159 3e10 +5 -3.5e-26
#
# this is more restrictive than awk's idea of a double literal
# (e.g. no 0x stuff)
this = ta[i[1]]
logg_dbg("read_atom", "token is " this)
if(this ~ /^(\+|-)?([0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?$/) {
return _number(this)
} else if(tolower(this) == "true") {
return _true()
} else if(tolower(this) == "false") {
return _false()
} else if(tolower(this) == "nil") {
return _nil()
} else if(this == ".") {
return "."
} else {
if(ta[i[1]] ~ "^\"") {
# strip quotes
return _string(substr(ta[i[1]], 2, length(ta[i[1]])-2))
} else {
return _symbol(ta[i[1]])
}
}
}
function _smoke_test_reader_tokenizer() {
tokenize_into(" ,~@(foo)", ta)
for(ti=1; ti<=length(ta); ti++) {
print "tokenarray[" ti "] = " ta[ti]
}
}
function _smoke_test_reader() {
x = read_str("(foo \"bar\" baz 3.14159 (sublist 1 2 3))", ta)
logg_inf("_smoke_test_reader", "final result: " x ", being " _repr(x))
}
# SPDX-License-Identifier: BSD-2-Clause
function _repr_list(lis, tv, t, v, pr, car, cdr, r) {
r = "" lis
for(; !_is_null(lis); lis=cdr) {
car = _car(lis)
cdr = _cdr(lis)
r = r " -> " cdr
}
return r
}
function _repr(cell, tv, t, v) {
if(_is_literal(cell)) {
if(cell ~ /^#/) return substr(cell,3)+0
else if(cell == "t") return "true"
else if(cell == "f") return "false"
else if(cell == "x") return "()"
else return "[repr unimplemented for literal " cell "]"
} else {
t = _TYPE[cell]
if(t == "(") return _repr_cons(cell)
else if(t == "s") return _repr_string(cell)
else if(t == "'") return _repr_symbol(cell)
}
}
function _repr_string(n) {
return "\"" _STRING[n] "\""
}
function _repr_cons(n, pr, car, cdr, r, tv, t, v) {
if(_is_null(n)) { # the empty list
return "()"
} else {
car = _car(n)
cdr = _cdr(n)
r = "(" _repr(car)
if(_is_null(cdr)) { # a list with length 1
return r ")"
} else if(_TYPE[cdr] == "(") { # a list
while(!_is_null(cdr)) {
n = cdr
car = _car(n)
cdr = _cdr(n)
r = r " " _repr(car)
}
r = r ")"
return r
} else { # a pair
return r " . " _repr(cdr) ")"
}
}
}
function _repr_symbol(n) {
return _SYM_NUMBERS[n]
}
function _print(x) {
print _repr(x)
}
#!/bin/sh
AWK=/usr/bin/awk
AWKOPTS=""
# If we have GNU awk, which can show its copyright (-C), use its
# "compatibility mode" (-c); One True Awk does not recognize -C and
# exits nonzero. We could just always use -c, but it would show some
# errors every time when run with One True Awk.
if $AWK -C >/dev/null 2>/dev/null; then AWKOPTS=" -c "; fi
the_awk_code=$(mktemp -t polyshawk.XXXXXXXX)
sed -n '/^# ---OTAWK CODE BELOW---/,$p' $0 > $the_awk_code
$AWK $AWKOPTS -f $the_awk_code "${@}"
exitcode=$?
rm -f $the_awk_code
return $?
: <<'#---ENDOTAWKCODE---qLOCcmbFZJd83aPhXW7l8TG4ybyFchUPuWPAjnNQLMheDr9KbHw'
# ---OTAWK CODE BELOW---
BEGIN { print "hi" }
#---ENDOTAWKCODE---qLOCcmbFZJd83aPhXW7l8TG4ybyFchUPuWPAjnNQLMheDr9KbHw
# SPDX-License-Identifier: BSD-2-Clause
# debugging print
function logg_setup() {
# default LOG_LEVEL if not specified using awk's -v switch
if(LOG_LEVEL == 0) LOG_LEVEL = 3
}
function logg_dbg(where, x) { if(LOG_LEVEL >= 3) print "DBG " where ": " x >"/dev/stderr" }
function logg_inf(where, x) { if(LOG_LEVEL >= 2) print "INF " where ": " x >"/dev/stderr" }
function logg_err(where, x) { if(LOG_LEVEL >= 1) print "ERR " where ": " x >"/dev/stderr" }
BEGIN {
logg_setup()
}
@shebang
# -*- coding: utf-8-unix; mode: awk; -*-
@include logging.awk
@include data.awk
@include eval.awk
@include reader.awk
@include printer.awk
@include repl.awk
# SPDX-License-Identifier: BSD-2-Clause
# this expects to go through a list of pairs, and to return one of the
# pairs' cdrs.
function _assoc(sym, alis, its, here, this_pair, rest_pairs, name, value) {
its = _nil()
logg_dbg("_assoc", "looking for " sym)
for(here=alis; !_is_null(here); here=rest_pairs) {
this_pair = _car(here)
rest_pairs = _cdr(here)
logg_dbg("_assoc", "is it " _repr(this_pair) "?")
name = _car(this_pair)
value = _cadr(this_pair)
logg_dbg("_assoc", "name is " name "; value is " value)
if(_truthy(_eq(name, sym))) {
logg_dbg("_assoc", "found. value is " value " containing " _repr(value))
its = value
break
} # otherwise we loop
} # if we have not found something, its still _nil()
return its
}
function _falsy(thing) {
# hmm, awk can't distinguish "0" and 0 i think
return ((thing == 0) || _is_null(thing) || (thing == "f"));
}
function _truthy(thing) {
return !_falsy(thing)
}
function _evcon(con, env) {
logg_dbg("_evcon", "con is " _repr(con))
if(_is_null(con)) {
return con
} else if(_truthy(_eval(_caar(con), env))) {
return _eval(_cadar(con), env)
} else {
return _evcon(_cdr(con), env)
}
}
function _bind(vars, args, env, s, tv, vcar, vcdr, acar, acdr) {
while(!_is_null(vars)) {
vcar = _car(vars)
vcdr = _cdr(vars)
acar = _car(args)
acdr = _cdr(args)
logg_dbg("_bind", "this var is " vcar " which is " _repr(vcar))
logg_dbg("_bind", "this arg is " acar " which is " _repr(acar))
logg_dbg("_bind", "env before: " env)
env = _cons(_cons(vcar, _cons(_eval(acar, env),_nil())), env)
logg_dbg("_bind", "env after: " env)
vars = vcdr
args = acdr
}
return env
}
function _append(la, lb, tv, t, v, as, acar, acdr) {
# i have iterativized many algorithms but not this one. by
# recursing, we effectively store pointers to traverse `la`
# backward, using the call stack.
logg_dbg("_append", "la " la "; lb " lb)
if(_is_null(la)) return lb
else {
return _cons(_car(la), _append(_cdr(la), lb))
}
}
function _eval(form, env, tv, t, v, n, cell, car, cdr, x) {
split(form, tv)
t = tv[1]
v = tv[2]
logg_dbg("_eval","form is " form " containing " _repr(form) "; env is " env " containing " _repr(env))
if(_is_literal(form)) {
# true, false, nil, and literal numbers evaluate to themselves
return form
} else {
# it's a string, symbol or cons.
t = _TYPE[form]
if(t == "(") {
car = _car(form)
if(_TYPE[car] == "'") { # (symbol-in-operator-position ...
if(car == _symbol("quote")) return _cadr(form)
else if(car == _symbol("atom"))
return _atom(_eval(_cadr(form), env))
else if(car == _symbol("car"))
return _car(_eval(_cadr(form), env))
else if(car == _symbol("cdr"))
return _cdr(_eval(_cadr(form), env))
else if(car == _symbol("cons"))
return _cons(_eval(_cadr(form), env), \
_eval(_caddr(form), env))
else if(car == _symbol("eq"))
return _eq(_eval(_cadr(form), env), \
_eval(_caddr(form), env))
else if(car == _symbol("cond"))
return _evcon(_cadr(form), env)
else if(car == _symbol("label")) {
logg_dbg("_eval label", "env before: " env "; appending " _cadr(cell))
env = _append(_cadr(form), env)
logg_dbg("_eval label", "env after: " env " containing " _repr(env))
return _eval(_caddr(form), env)
} else if(car == _symbol("lambda")) return form
else return _eval(_cons(_eval(car, env), cdr),env)
} else if(_TYPE[car] == "(") {
cdr = _cdr(form)
if(_car(car) == _symbol("lambda")) {
# form is like ( (lambda (v1 v2...) body) arg1 arg2 )
# car is the whole lambda form; cdr is the args
#
logg_dbg("_eval lambda", "body is " _caddr(car) " containing " _repr(_caddr(car)))
logg_dbg("_eval lambda", "variable list is " _cadr(car) " containing " _repr(_cadr(car)))
logg_dbg("_eval lambda", "argument list is " cdr " which is " _repr(cdr))
return _eval(_caddr(car), _bind(_cadr(car), cdr, env))
} else {
logg_dbg("_eval", "evaluating list in function position: " _repr(car))
return _eval(_cons(_eval(car, env), cdr), env)
}
} else {
logg_err("_eval", "unexpected thing in function position: " _repr(car))
return _nil()
}
} else if(t == "'") {
logg_dbg("_eval", "evaluating symbol " _SYM_NUMBERS[form])
return _assoc(form, env)
} else if(t == "s") {
return form
} else {
logg_err("_eval", "how do i eval " t " ?")
exit 1
}
}
}
# SPDX-License-Identifier: BSD-2-Clause
BEGIN {
N = 0
n_conses = 0
}
function _string(s) {
_STRING[++N] = s
_TYPE[N] = "s"
return N
}
function _defined_symbol(name) {
if(name in _SYM_NAMES) {
return _SYM_NAMES[name]
} else {
logg_err("symbol undefined: " name)
exit 1
}
}
function _symbol(name) {
if(name in _SYM_NAMES) {
return _SYM_NAMES[name]
} else {
_SYM_NUMBERS[++N] = name
n_conses += 1
_SYM_NAMES[name] = N
_TYPE[N] = "'"
logg_dbg("_symbol", name " interned as symbol number " N)
return N
}
}
function _nil() {
return "x"
}
function _true() {
return "t"
}
function _false() {
return "f"
}
function _number(value) {
return "# " value
}
function _is_literal(value, r) {
# A value we have stored literally will be a string ("x", "t",
# "f", "# 0", as just above); adding the number 0 to it will
# result in the number 0, not the value itself. A solitary number
# is an index into the _TYPE, _CAR/_CDR, _SYM_NUMBERS and/or
# _STRING; such a bare number plus 0 equals itself.
return ((value+0)!=value)
}
function _atom(value, t) {
if(_is_literal(value)) {
return _true();
} else {
t = _TYPE[value]
if(t == "'") return _true();
else if(t == "s") return _true();
else return _false();
}
}
function _eq(a,b) {
if(_is_literal(a)) {
if(_is_literal(b)) {
return (a == b) ? _true() : _false();
} else {
return _false();
}
} else {
if(_TYPE[a] == _TYPE[b]) {
return (a == b) ? _true() : _false();
} else {
return _false();
}
}
}
function _cons(car, cdr, contents) {
_CAR[++N] = car
_CDR[N] = cdr
_TYPE[N] = "("
logg_dbg("_cons", N " has car " car " and cdr " cdr)
return N
}
function _set_car(cons_index, newcar) {
_CAR[cons_index] = newcar
}
function _set_cdr(cons_index, newcdr) {
_CDR[cons_index] = newcdr
}
function _car(cons_index) {
return _CAR[cons_index]
}
function _cdr(cons_index) {
return _CDR[cons_index]
}
function _caar(cons_index, tv, t, v) {
return _car(_car(cons_index))
}
function _cadr(cons_index, cdr) {
return _car(_cdr(cons_index))
}
function _cdar(cons_index) {
return _cdr(_car(cons_index))
}
function _caadr(cons_index, tv, t, v) {
return _car(_car(_cdr(cons_index)))
}
function _caddr(cons_index, tv, t, v) {
return _car(_cdr(_cdr(cons_index)))
}
function _cadar(cons_index) {
return _car(_cdr(_car(cons_index)))
}
function _nreverse(lis, old_head, last_cdr, car, cdr) {
old_head = lis
last_cdr = _nil()
logg_dbg("_nreverse", "hi. lis is " lis)
logg_dbg("_nreverse", "the representation of lis is " _repr(lis))
# if the list is null, this while body will happen zero times
while(!_is_null(lis)) {
logg_dbg("_nreverse", "lis is " lis " containing " _repr(lis))
if(_TYPE[lis] == "(") {
logg_dbg("_nreverse", "lis is cons " lis \
" which contains " _CAR[lis] " . " _CDR[lis])
cdr = _cdr(lis)
_set_cdr(lis, last_cdr)
last_cdr = lis
lis = cdr
} else {
logg_err("_nreverse", "cannot reverse a non-list")
exit 1
}
}
# now last_cdr is the initially last item.
return last_cdr
}
function _is_null(c) {
return (c == _nil());
}
function _smoke_test_core() {
x = _cons(_T, _cons(_string("foo"), _cons(_symbol("foo"), _NIL)))
print x
print _STR[1]
print _repr(x)
}
* What is it?
No, not an OpenGL implementation in awk. It's a Lisp interpreter
written in the AWK dialect understood by the One True Awk‡. I started
by following directions from the [[https://github.com/kanaka/mal][MAL (Make a Lisp) project]]—which has
an implementation written in GNU awk—but before getting far, I took a
left turn into Nils Holm's [[https://t3x.org/lfn/index.html][_Lisp from Nothing_]] (ISBN
978-1-71650-883-7).
** ‡One True Awk?
Yes—"the version of awk described in The AWK Programming Language,
Second Edition, by Al Aho, Brian Kernighan, and Peter Weinberger
(Addison-Wesley, 2024, ISBN-13 978-0138269722, ISBN-10 0138269726)."
This is the [[https://github.com/onetrueawk/awk][One True Awk]], also known as BWK awk or ~nawk~.
Importantly for our purposes here, this is the implementation of awk
that is included in the FreeBSD base system.
Around 1988, GNU awk, aka ~gawk~, was released; as is often the case
with GNU programs, it has many more features, and it's made available
under the GPL. Most notably, it has multidimensional arrays, of which
the MAL Awk implementation makes heavy use; but the One True Awk
doesn't have those.
There are other [[view-source:https://en.wikipedia.org/wiki/AWK#Versions_and_implementations][Awk implementations]], but none of them are discussed
here further.
* What's it for?
I envision deploying configurations to BSD machines with this. That's
why it's so important that it work with the ~awk~ that's in the base
system, rather than GNU awk. The [[https://wryun.github.io/es-shell/][es extensible shell]], based on Plan
9's ~rc~ but with first-class functions, does /beautifully/ at this
job, but it has an occasional segfault, and errors in ~es~ scripts can
be hard to locate, and it's not being maintained. A Lisp should be
more readable, easier to improve, and more approachable.
* Pull the other one, it's got bells on.
Well, it seemed like a good idea at the time.
* All right, how do I use it then?
See below on the language understood by this interpreter.
** Building and running
Run ~make~. It will build all the source into one file, ~glotawk~. You
can run that one, ~./glotawk~, and it'll show you a REPL prompt.
** Testing
~make test~ or ~make check~; either is the same.
** Enhancing and redistributing
As detailed in the LICENSE file, this software is made available to
you under the terms of the 2-clause BSD license.
* Contributing
This code is kept in a Pijul repository. If you have changes or
enhancements, talk to me on Mastodon @jaredjennings@mastodon.bsd.cafe,
or email jjennings@fastmail.fm.
Avoid constructs peculiar to GNU awk, GNU make, and GNU bash: not to
be partisan, but to be minimalist. With luck, it might work on Busybox
awk or some other even smaller thing.
* More odd stuff
https://j.agrue.info/
* What dialect is this?
Well, for atoms, you get
- ~true~
- ~false~
- ~nil~ which equals ~()~
- numbers (which are Awk numbers, which in turn are double-precision
floats)
- double-quoted strings
- interned symbols.
For data structures, you get
- pairs, like ~(first . second)~
- lists, like ~(a b c)~
Special forms are
- atom
- quote
- car
- cdr
- cons
- eq
- cond
- label
- lambda
~cond~ checks whether things are truthy, and that means not nil and
not false.
* Colophon
Say "glow talk," or "glot awk," I don't care which. A polyglot is
someone who knows multiple languages, and the One True Awk can be
abbreviated as otawk. Some people may feel like Lisp glows. Also the
word is short, and there are no indications from my favorite search
engine that anyone else is using the word, so unique.
# SPDX-License-Identifier: BSD-2-Clause
all: glotawk
INCLUDES != grep '^@include' glotawk.tmpl.awk | awk '{print $$2}'
glotawk: tmpl.awk glotawk.tmpl.awk $(INCLUDES)
awk -f tmpl.awk glotawk.tmpl.awk > $@
chmod a+x $@
test: check
check: glotawk sane_lisp
if ./sane_lisp; then echo --pass--; else echo --FAIL--; fi
clean:
rm -f glotawk
.PHONY: all check test clean
Copyright 2025 Jared Jennings
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
.git
.DS_Store
*~
glotawk