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_switchesnext}/^@include/ {fn = $2print "# --- " fnwhile((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_lispLOG_LEVEL=1# vvv https://github.com/dnmfarrell/tap.sh/TAP_TEST_COUNT=0TAP_FAIL_COUNT=0tap_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 1exit $((TAP_FAIL_COUNT > 0)) # C semantics}tap_ok() {if [ "$1" -eq 0 ]; thentap_pass "$2"elsetap_fail "$2"fi}tap_cmp() {if [ "$1" = "$2" ]; thentap_pass "$3"elsetap_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 ] ; thentap_fail "$3 - nonzero exit code"elsetap_cmp "$output" "$2" "$3"fi}if [ "$#" -gt 0 ]; thenwhile [ "$#" -gt 0 ]; doif [ "$1" = "-v" ]; thenif [ $LOG_LEVEL -lt 3 ]; thenLOG_LEVEL=$(( $LOG_LEVEL + 1 ))fifishiftdonefiTEST_COUNT=0echo '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-Clausefunction _print_with_no_ors(x, sors) {sors=ORSORS=""print xORS=sorsfflush()}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-Clausefunction tokenize_into(input, ta, chars_left, tal, mp) {chars_done = 0chars_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 charta[++tal] = substr(input, 1, RLENGTH)} else if(match(input, /^"(\\.|[^\\"])*"?/)) { # double-quoted stringta[++tal] = substr(input, 1, RLENGTH)} else if(match(input, /^;.*/)) { # commentta[++tal] = substr(input, 1, RLENGTH)} else if(match(input, /^[^ \[\]{}('"`,;)]+/)) { # non-special charsta[++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 1input = substr(input, 1+RLENGTH)chars_left -= RLENGTHchars_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 -ctokenize_into(s, ta)tal = length(ta)i[1] = 1 # make i an array so we can pass it by referencereturn 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] += 1return 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 = headhead = _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 terminatedlogg_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 quotesreturn _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-Clausefunction _repr_list(lis, tv, t, v, pr, car, cdr, r) {r = "" lisfor(; !_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)+0else 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 listreturn "()"} else {car = _car(n)cdr = _cdr(n)r = "(" _repr(car)if(_is_null(cdr)) { # a list with length 1return r ")"} else if(_TYPE[cdr] == "(") { # a listwhile(!_is_null(cdr)) {n = cdrcar = _car(n)cdr = _cdr(n)r = r " " _repr(car)}r = r ")"return r} else { # a pairreturn r " . " _repr(cdr) ")"}}}function _repr_symbol(n) {return _SYM_NUMBERS[n]}function _print(x) {print _repr(x)}
#!/bin/shAWK=/usr/bin/awkAWKOPTS=""# 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 "; fithe_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_codereturn $?: <<'#---ENDOTAWKCODE---qLOCcmbFZJd83aPhXW7l8TG4ybyFchUPuWPAjnNQLMheDr9KbHw'# ---OTAWK CODE BELOW---BEGIN { print "hi" }#---ENDOTAWKCODE---qLOCcmbFZJd83aPhXW7l8TG4ybyFchUPuWPAjnNQLMheDr9KbHw
# SPDX-License-Identifier: BSD-2-Clause# debugging printfunction logg_setup() {# default LOG_LEVEL if not specified using awk's -v switchif(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 = valuebreak} # 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 thinkreturn ((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 = vcdrargs = 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 lbelse {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 themselvesreturn 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 formelse 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-ClauseBEGIN {N = 0n_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] = namen_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 = lislast_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 timeswhile(!_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 = lislis = 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 xprint _STR[1]print _repr(x)}
* What is it?No, not an OpenGL implementation in awk. It's a Lisp interpreterwritten in the AWK dialect understood by the One True Awk‡. I startedby following directions from the [[https://github.com/kanaka/mal][MAL (Make a Lisp) project]]—which hasan implementation written in GNU awk—but before getting far, I took aleft turn into Nils Holm's [[https://t3x.org/lfn/index.html][_Lisp from Nothing_]] (ISBN978-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 awkthat is included in the FreeBSD base system.Around 1988, GNU awk, aka ~gawk~, was released; as is often the casewith GNU programs, it has many more features, and it's made availableunder the GPL. Most notably, it has multidimensional arrays, of whichthe MAL Awk implementation makes heavy use; but the One True Awkdoesn't have those.There are other [[view-source:https://en.wikipedia.org/wiki/AWK#Versions_and_implementations][Awk implementations]], but none of them are discussedhere further.* What's it for?I envision deploying configurations to BSD machines with this. That'swhy it's so important that it work with the ~awk~ that's in the basesystem, rather than GNU awk. The [[https://wryun.github.io/es-shell/][es extensible shell]], based on Plan9's ~rc~ but with first-class functions, does /beautifully/ at thisjob, but it has an occasional segfault, and errors in ~es~ scripts canbe hard to locate, and it's not being maintained. A Lisp should bemore 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 runningRun ~make~. It will build all the source into one file, ~glotawk~. Youcan run that one, ~./glotawk~, and it'll show you a REPL prompt.** Testing~make test~ or ~make check~; either is the same.** Enhancing and redistributingAs detailed in the LICENSE file, this software is made available toyou under the terms of the 2-clause BSD license.* ContributingThis code is kept in a Pijul repository. If you have changes orenhancements, 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 tobe partisan, but to be minimalist. With luck, it might work on Busyboxawk or some other even smaller thing.* More odd stuffhttps://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-precisionfloats)- 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 andnot false.* ColophonSay "glow talk," or "glot awk," I don't care which. A polyglot issomeone who knows multiple languages, and the One True Awk can beabbreviated as otawk. Some people may feel like Lisp glows. Also theword is short, and there are no indications from my favorite searchengine that anyone else is using the word, so unique.
# SPDX-License-Identifier: BSD-2-Clauseall: glotawkINCLUDES != 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: checkcheck: glotawk sane_lispif ./sane_lisp; then echo --pass--; else echo --FAIL--; ficlean:rm -f glotawk.PHONY: all check test clean
Copyright 2025 Jared JenningsRedistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions aremet: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 copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ANDCONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AREDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ORSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESSINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OFLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUTOF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF SUCH DAMAGE.
.git.DS_Store*~glotawk