HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
John-Nagle
nqthm
Common Lisp2016–2026
John-Nagle / nqthm

nqthm

nqthm - the original Boyer-Moore theorem prover, from 1992

2Distinct people
20Mentions
0Explicit recommenders
61GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

nqthm - the original Boyer-Moore theorem prover, from 1992

GitHub

Where it is used

No documented product use has been verified yet.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 8–Nov 15 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/15/2026, 12:45:45 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year20 dated mentions

Observed mentions: 2016-08-07 04:41:56 UTC to 2026-10-14 02:32:10 UTC.

0123452016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 4 mentions; 1 submissions, 3 comments20162017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments2018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20212022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC413
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC505
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC000
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC101
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC101
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC101
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC101
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC101
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC303
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC101
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC202

What Hacker News says

5 sources
Having been down this road [1], a big problem is that the people doing this tend to be too much into the theory and not enough into wanting programs not to crash. That's some of the Coq world. It doesn't have to be this way. Microsoft's work in this area led to the Static Driver Verifier [2], which was a huge success. Windows crashes due to kernel drivers breaking the rest of the kernel have pretty much disappeared. They were once (Windows XP and earlier) responsible for over half of Windows crashes. Most of the things you want to prove about programs to make sure they don't crash are mathematically trivial. Detecting buffer overflows and off-by-one errors seldom requires elaborate proofs. If they do, your program design probably has a problem. Simple proofs can be handled by a prover which uses a Nelson-Oppen decision procedure. This i
Read the rest
s a complete decision procedure for formulas which contain only addition, subtraction, multiplication by constants, Boolean operators, structures, and arrays. This covers most data access operations. It's fully automatic and complete - the algorithm always succeeds within its problem space. You need one of those for the easy parts. For the hard parts, we used the Boyer-Moore prover. (Sources are available and I brought it back to life on Github.[3] It runs 3000x faster than it did in 1982.) The interface with the language is crucial to usability. Most efforts get this wrong. They put the verification stuff in separate files, or in comments. It should be part of the normal code. We built the verification language into the compiler, so you used the same source and the same syntax for compiling and verification. There were additional statements - ASSERT, ENTER, EXIT, etc. The same compiler front end was used for both compiling and verification. The verifier itself ran off the byte code from the compiler front end. All type and size issues had been decided at that point, so the verifier was looking at unambiguous operations such as "pop two operands from stack, add in 16-bit twos complement arithmetic, push on stack". This captured the semantics of the program the same way the compiler did. The user never sees the easy stuff. Every subscript use generates a bounds check assertion which has to be proved. If the prover is able to prove the assertion true, the user doesn't see it. Only the errors are visible. Another trick is that you don't write "assert(a and b and and d)", you write "assert(a); assert(b);". Those generate separate proof goals, and if only one fails,the user gets error messages about only that one. Also, when you write "assert(a); assert(b);", the second one is proved under the assumption the first one is true. This prevents cascading errors. A key point here is that verification is meaningless unless you prove all the theorems. If you write "assert(false)" other errors go away, and you're left with one simple error, which you cannot eliminate. You don't get to dismiss it. It's possible to write an assertion that's equivalent to "assert(false);", such as "assert(0 > 1)"; This is why I'm so negative on the arguments from some of the Rust crowd that a little undefined behavior is OK. It can blow at any seam. When you needed to prove something hard, you wrote assert(a); assert(b); where a could be proved by the Nelson-Oppen prover from the code before it. B was chosen such that if you assumed b, the Nelson-Oppen prover could prove anything required past that point. Now you had one error message, that the system couldn't prove "a implies b". So then someone had to try to prove that with the Boyer-Moore prover. The Boyer-Moore prover is mostly automatic, but often someone had to provide intermediate theorems to get to a hard one. Those were all machine-checked. If this succeeded, the Boyer-Moore prover generated a theorem that a implies b for use by the verifier. Rerunning the program verifier would then succeed. Such theorems could be reused with different variable bindings, so if the same problem came up again, it didn't require more work with the Boyer-Moore system. If you're generating vast amounts of user-visible proof work, something is wrong. The whole system I'm describing is now on Github.[4] It's decades-old code, but I've ported about 80% of it to modern compilers. The Franz LISP to Common LISP port of the prover isn't working well, and the author of that code died years ago. If anyone wants to work on that, go ahead. [1] http://www.animats.com/papers/verifier/verifiermanual.pdf [2] https://docs.microsoft.com/en-us/windows-hardware/drivers/de... [3] https://github.com/John-Nagle/nqthm [4] https://github.com/John-Nagle/pasv
In Formal Verification: The Gap Between Perfect Code and Reality
It's good to know that Boyer and Moore are still at it. I used their previous system, Nqthm, many years ago. I recently got it running from the 1992 sources and put it on Github.[1] It's nice seeing it run a thousand times faster. I still like their logic system. Constructive mathematics has a basic soundness you don't get from systems with too many axioms. [1] https://github.com/John-Nagle/nqthm
In ACL2
I had a run-in with this issue once. Many years ago, when working on a program verification system, I had to show that two theorem provers (Boyer-Moore and Nelson-Oppen) were working on compatible theories. If that was botched, the system could prove false statements. So I undertook to prove, in Boyer-Moore theory, the "axioms" of arrays used in the Nelson-Oppen prover. McCarthy defined four classic axioms for arrays, and here are the first two. p=r ⇒ read(write(a, p, v), r) = v ¬(p=r) ⇒ read(write(a, p, v), r) = read(a, r) This just says that if you store v at a[p], and then read it back, you get what you put there, and you don't change any other elements. So I set this up in Boyer-Moore notation. (PROVE-LEMMA SELECT-OF-STORE-1 (REWRITE) (IMPLIES (AND (arrayp A) (NUMBERP I)) (EQUAL (selecta (stor
Read the rest
ea A I V) I) V))) (PROVE-LEMMA SELECT-OF-STORE-2 (REWRITE) (IMPLIES (AND (arrayp A) (NUMBERP I) (NUMBERP J) (NOT (EQUAL I J))) (EQUAL (selecta (storea A I V) J) (selecta A J)))) But Boyer-Moore theory is constructive; everything must have a definition that can be evaluated as a function. So I had to define arrays and the "selecta" and "storea" functions so that this would work. I could do this by defining an array as a list of (subscript, value) tuples. (ADD-SHELL UNDEFINED-OBJECT UNDEFINED UNDEFINEDP ()) (ADD-SHELL array-shell empty-array array-recognizer ((array-elt-value (NONE-OF) UNDEFINED) (array-elt-subscript (ONE-OF NUMBERP) ZERO) (array-prev (ONE-OF array-recognizer) empty-array))) This is simply a structure definition for a list. An array-shell has an array-elt-value, an array-elt-subscript, and array-prev, which is the rest of the elements. "array-shell" is the constructor, "empty-array" is essentially "nil" for this type, and "array-recognizer" is a predicate which is true for objects of type array-shell. For array equality to work, the list of tuples had to be ordered by subscript, so that two arrays with the same values would always have the same representation. So selecta and storea are complicated. selecta is a recursive lookup in a list: (DEFN selecta (A I) (IF (EQUAL (array-elt-subscript A) I) (array-elt-value A) (IF (EQUAL (array-prev A) (empty-array)) (UNDEFINED) (selecta (array-prev A) I)))) Store into array is ugly, because it's an update of an ordered list. (DEFN storea (A I V) (IF (NOT (arrayp A)) (empty-array) (IF (NOT (NUMBERP I)) A (IF (EQUAL A (empty-array)) (IF (EQUAL V (UNDEFINED)) (empty-array) (array-shell V I (empty-array))) (IF (EQUAL (array-elt-subscript A) I) (IF (EQUAL V (UNDEFINED)) (array-prev A) (array-shell V I (array-prev A))) (IF (LESSP (array-elt-subscript A) I) (IF (EQUAL V (UNDEFINED)) A (array-shell V I A)) (array-shell (array-elt-value A) (array-elt-subscript A) (storea (array-prev A) I V)))))))) This recursive function is complicated enough that it needs a lemma to help with proof of termination. (PROVE-LEMMA storea-is-sound (REWRITE) (implies (and (arrayp a) (not (equal a (empty-array)))) (lessp (count (array-prev a)) (count a)))) This says that the array-prev list component of a is always shorter than a. The Boyer-Moore prover must prove termination for all defined functions, which it does by examining any recursive call and proving that there is a measure defined on the arguments which is a smaller nonnegative integer. For the easy cases, the prover can do this automatically, but sometimes you have to define a lemma to help it. The lemma, of course, also has to be proved. So, given all that, the Boyer-Moore theorem prover could be turned loose to grind out proofs of the rules for arrays. It took about 45 minutes of grinding in 1982 on a 1 MIPS VAX 11/780. You can rerun it now. Here's the prover, which requires clisp.[1]. Here's the entire set of definitions and theorems we used for program verification.[2]. Read the instructions at [1], and get the system up to where it's done "boot-strap". Then load the file at [2]. Long, detailed, human-readable explanations of the proof steps are displayed. The entire proof run for all those basic theorems takes about a second, and that's probably mostly scrolling. So, having rigorously proved the "axioms" for arrays from first principles, I submitted a paper to JACM. The reviewer comments I got back were mainly about the uglyness of the approach. All that case analysis! One comment was that the reviewer was upset about having a proof that ugly at the foundations of computer science. The paper was rejected. The reason it's ugly is that it doesn't get to use set theory. Informally, a set is a collection whose order does not matter. "Order does not matter" is not a meaningful concept in strict Boyer-Moore theory. If order matters, there are many cases to prove, stemming from all those IF statements in the definitions. A generation later, fewer mathematicians would be bothered by this form of "uglyness". But back then, machine proofs were so rare that this was considered awful. Also, now that computers can do this in a second, complaining that it's too complicated seems whiny. [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In What makes maths beautiful?
There's another approach, from Boyer and Moore. Boyer and Moore built up mathematics from constructs at the Peano axiom level (zero, add1, etc.) plus recursive functions that must terminate. It's constructive mathematics; there are no quantifiers, no ∀ or ∃. [1] They built an automatic theorem prover in the 1970s and 1980s that works on this theory. (I recently made it work on Gnu Common LISP and put it on Github, so people can run it again.)[2] In Boyer-Moore theory, all functions are total - you can apply any function to any object. Types are predicates. Here's a definition of ORDERED for a list of number: (DEFN ORDERED (L) (IF (LISTP L) (IF (LISTP (CDR L)) (IF (LESSP (CADR L) (CAR L)) F (ORDERED (CDR L))) T) T)) If
Read the rest
L is not a list, it is considered to be ordered. This makes it a total function, runnable on any input, even though the result for the "wrong" type is not useful. This provides the benefits of types without requiring a theory of types. It's a very clean way to look at the foundations of mathematics. It's simpler than Russell and Whitehead. When you prove things using definitions like this, there's a lot of case analysis. This gets worse combinatorially; a theorem with four variables with type constraints will generate at least 2⁴ cases, only one of which is interesting. Most of the cases, such as when L is not a list, are trivial, but have to be analyzed. Computers are great at this, and the Boyer-Moore prover deals with those cases without any trouble. But it went against a long tradition in mathematics of avoiding case analysis. That made this approach unpopular with the generation of pre-computer mathematicians. Today, it would be more acceptable. (It's fun to run the Boyer-Moore prover today. In the 1980s, it took 45 minutes to grind through the basic theory of numbers. Now it takes a few seconds.) [1] https://www.amazon.com/Computational-Logic-Robert-S-Boyer/dp... [2] https://github.com/John-Nagle/nqthm
In Where Do Type Systems Come From?
It's amusing that they talk about Boyer-Moore. They mean, I think, the string search algorithm, not the theorem prover. Their string search algorithm was just intended as something Boyer and Moore could use as a hard case for code proofs. Their real work was the original Boyer-Moore theorem prover, their constructive theory of mathematics, their book "A Computational Logic", and their later ACL prover. (I used to know Boyer and Moore, and used their theorem prover. Recently I took the original sources from the final 1992 version, put them on Github, and got it working with the current GNU Common LISP.[1] So you can now run their prover from the 1980s several thousand times faster than in the VAX days. It's neat to see it prove the basic theorems of number theory in about a second.) [1] https://github.com/John-Nagle/nqthm
In Hey programmers, we need to talk (2013)

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.