Skip to main content

NSR-L Language Reference

NSR-L (Neuro-Symbolic Reasoning Language) is a Prolog-inspired logic programming language designed for symbolic reasoning, constraint specification, and knowledge representation in AI systems.

Table of Contents

  1. Overview
  2. Lexical Elements
  3. Terms
  4. Logical Expressions
  5. Statements
  6. Operators
  7. Built-in Predicates
  8. List Operations
  9. Arithmetic
  10. Constraints
  11. Negation
  12. Quantifiers
  13. Comments
  14. Complete Grammar
  15. Examples

Overview

NSR-L combines classical logic programming with modern features for AI guardrails:
  • First-order logic with variables, predicates, and quantifiers
  • Prolog-style rules with Head :- Body syntax
  • Classical and non-monotonic negation (~ and \+)
  • Hard and soft constraints for flexible validation
  • Arithmetic evaluation with the is operator
  • List operations for data manipulation
  • Belief revision with confidence intervals

Hello World

% Facts
human(socrates).
human(plato).

% Rules
mortal(X) :- human(X).

% Query
?- mortal(socrates).

Lexical Elements

Atoms

Atoms are constant identifiers. They must start with a lowercase letter or be quoted.
% Unquoted atoms (start with lowercase)
hello
customer_service
product123
x

% Quoted atoms (any characters)
'Hello World'
'contains spaces'
'special-chars!'

Variables

Variables start with an uppercase letter or underscore.
X              % Regular variable
Name           % Descriptive variable
CustomerID     % CamelCase variable
_              % Anonymous variable (matches anything, not bound)
_Unused        % Named but intentionally unused
Important: Variables starting with _ suppress singleton variable warnings.

Numbers

42             % Integer
-17            % Negative integer
3.14           % Float
-0.5           % Negative float
1.0e10         % Scientific notation

Strings

"hello world"           % Double-quoted string
"contains \"quotes\""   % Escaped quotes
"line1\nline2"          % Escape sequences

Terms

Terms are the fundamental data structures in NSR-L.

Constants

atom           % Atom constant
42             % Integer constant
3.14           % Float constant
"string"       % String constant

Variables

X              % Unbound variable
_              % Anonymous variable

Compound Terms (Functors)

person(john, 30)                    % Functor with arguments
node(left(a), right(b))             % Nested structure
pair(X, Y)                          % With variables
address(city("NYC"), zip(10001))    % Complex nesting

Lists

Lists use square bracket notation with | for head/tail decomposition.
[]                        % Empty list
[a, b, c]                 % List of atoms
[1, 2, 3]                 % List of integers
[X, Y, Z]                 % List of variables
[Head | Tail]             % Head/tail decomposition
[A, B | Rest]             % First two elements + rest
[[1,2], [3,4]]            % Nested lists

Logical Expressions

Atomic Propositions

predicate(arg1, arg2, ...)
Examples:
human(socrates)
parent(tom, mary)
age(john, 30)
product(wide_awake, eye_cream)

Conjunction (AND)

Use & or , for conjunction:
human(X) & mortal(X)          % Using &
human(X), mortal(X)           % Using ,
parent(X, Y) & parent(Y, Z)   % Grandparent pattern

Disjunction (OR)

Use | or ; for disjunction:
male(X) | female(X)           % Using |
cat(X) ; dog(X)               % Using ;

Implication

Head :- Body                  % Prolog-style rule
human(X) -> mortal(X)         % Arrow notation
A <-> B                       % Biconditional (iff)

Negation

~positive(X)                  % Classical negation (strong)
\+ found(X)                   % Negation as failure (weak)
not(present(X))               % Alternative NAF syntax

Statements

NSR-L programs consist of three types of statements:

Facts

Facts are ground assertions (no variables, or universally quantified).
human(socrates).
product(wide_awake, eye_cream).
return_window_days(30).
prohibited_phrase("contact the manufacturer").
~ships_internationally.           % Negative fact

Rules

Rules define logical implications with head and body.
% Basic rule
mortal(X) :- human(X).

% Rule with conjunction
grandparent(X, Z) :- parent(X, Y) & parent(Y, Z).

% Rule with disjunction
animal(X) :- cat(X) | dog(X) | bird(X).

% Rule with negation
eligible(X) :- member(X) & ~suspended(X).

% Rule with comparison
adult(X) :- age(X, Age) & Age >= 18.

% Complex rule
response_valid(R) :-
    ~response_invalid(R) &
    ~contains_internal_reasoning(R) &
    ~promises_replacement(R).

Queries

Queries ask the system to prove or find solutions.
?- mortal(socrates).              % Yes/no query
?- parent(X, mary).               % Find X
?- ancestor(X, Y).                % Find all pairs
?- age(john, Age), Age > 18.      % Query with condition

Operators

Logical Operators

OperatorMeaningExample
&Conjunction (AND)a(X) & b(X)
,Conjunction (AND)a(X), b(X)
|Disjunction (OR)a(X) | b(X)
;Disjunction (OR)a(X) ; b(X)
~Classical negation~alive(X)
\+Negation as failure\+ found(X)
notNegation as failurenot(found(X))
->Implicationhuman(X) -> mortal(X)
:-Rule implicationhead :- body
<->Biconditionala <-> b

Comparison Operators

OperatorMeaningExample
=UnificationX = foo
\=Not unifiableX \= Y
==IdenticalX == Y
\==Not identicalX \== Y
<Less thanX < 10
>Greater thanX > 0
=< or <=Less or equalX =< 100
>=Greater or equalX >= 0
=:=Arithmetic equalX + 1 =:= Y
=\=Arithmetic not equalX =\= 0

Arithmetic Operators

OperatorMeaningExample
+AdditionX + Y
-SubtractionX - Y
*MultiplicationX * Y
/DivisionX / Y
//Integer divisionX // Y
mod or %ModuloX mod Y

Operator Precedence (lowest to highest)

  1. :-, ->, <->
  2. ;, |
  3. ,, &
  4. \+, not, ~
  5. =, \=, <, >, =<, >=, =:=, =\=
  6. +, -
  7. *, /, //, mod
  8. Unary -

Built-in Predicates

Unification & Comparison

X = Y                  % Unify X and Y
X \= Y                 % X and Y do not unify
X == Y                 % X and Y are identical
X \== Y                % X and Y are not identical

Type Checking

atom(X)                % X is an atom
number(X)              % X is a number
integer(X)             % X is an integer
float(X)               % X is a float
compound(X)            % X is a compound term
is_list(X)             % X is a list
var(X)                 % X is unbound
nonvar(X)              % X is bound
ground(X)              % X contains no variables

Arithmetic Evaluation

X is Expr              % Evaluate Expr and bind to X

% Examples:
Sum is 2 + 3.          % Sum = 5
Prod is X * Y.         % Prod = X * Y
Mod is 17 mod 5.       % Mod = 2

Arithmetic Functions

abs(X)                 % Absolute value
min(X, Y)              % Minimum
max(X, Y)              % Maximum
sqrt(X)                % Square root
X ** Y                 % Power (X^Y)
sin(X), cos(X), tan(X) % Trigonometric
log(X), exp(X)         % Logarithm, exponential
floor(X), ceiling(X)   % Rounding
round(X)               % Round to nearest integer

Control

!                      % Cut (commit to current choice)
true                   % Always succeeds
false                  % Always fails
fail                   % Always fails

List Operations

NSR-L provides Prolog-standard list predicates.

member/2

Check if element is in list, or enumerate elements.
member(X, [a, b, c])           % X = a; X = b; X = c
member(b, [a, b, c])           % true
member(d, [a, b, c])           % false

append/3

Concatenate lists or split a list.
append([1,2], [3,4], Result)   % Result = [1,2,3,4]
append(X, [4,5], [1,2,3,4,5])  % X = [1,2,3]
append(X, Y, [a,b,c])          % Generate all splits

length/2

Get or check list length.
length([a,b,c], N)             % N = 3
length(L, 5)                   % L = [_,_,_,_,_]

reverse/2

Reverse a list.
reverse([1,2,3], R)            % R = [3,2,1]

nth0/3, nth1/3

Access element by index.
nth0(0, [a,b,c], X)            % X = a (0-indexed)
nth1(1, [a,b,c], X)            % X = a (1-indexed)
nth0(Index, [a,b,c], b)        % Index = 1

last/2

Get last element.
last([a,b,c], X)               % X = c

sort/2, msort/2

Sort lists.
sort([3,1,2,1], S)             % S = [1,2,3] (removes duplicates)
msort([3,1,2,1], S)            % S = [1,1,2,3] (keeps duplicates)

sumlist/2

Sum numeric elements.
sumlist([1,2,3,4], Sum)        % Sum = 10

flatten/2

Flatten nested lists.
flatten([[1,2],[3,[4,5]]], F)  % F = [1,2,3,4,5]

permutation/2

Generate or check permutations.
permutation([1,2,3], P)        % P = [1,2,3]; P = [1,3,2]; ...

Arithmetic

The is Operator

Evaluates arithmetic expressions and binds result.
X is 2 + 3.                    % X = 5
Y is X * 2.                    % Y = 10
Z is sqrt(16).                 % Z = 4.0

Arithmetic Comparisons

X < Y                          % X less than Y
X > Y                          % X greater than Y
X =< Y                         % X less than or equal to Y
X >= Y                         % X greater than or equal to Y
X =:= Y                        % X arithmetically equal to Y
X =\= Y                        % X arithmetically not equal to Y

Examples

% Check if adult
adult(Person) :-
    age(Person, Age),
    Age >= 18.

% Calculate discount
final_price(Product, Price) :-
    base_price(Product, Base),
    discount(Product, Percent),
    Price is Base * (100 - Percent) / 100.

% Factorial (recursive)
factorial(0, 1).
factorial(N, F) :-
    N > 0,
    N1 is N - 1,
    factorial(N1, F1),
    F is N * F1.

Constraints

NSR-L supports hard, soft, and weighted constraints for validation.

Constraint Types

TypeBehaviorUse Case
HardViolation = rejectionProhibited phrases, security rules
SoftViolation = confidence penaltyStyle guidelines, recommendations
WeightedContributes to scoreOptimization objectives

Defining Constraints in Rules

% Hard constraint: Never say prohibited phrases
response_invalid(R) :-
    prohibited_phrase(P) &
    contains_text(R, P).

% Soft preference: Include link when relevant
should_include_link(R, Link) :-
    topic_detected(R, Topic) &
    required_link(Topic, Link) &
    ~contains_text(R, Link).

Constraint API (HTTP)

{
  "id": "no_prohibited_phrases",
  "description": "Response must not contain prohibited phrases",
  "strength": {"type": "hard"},
  "expression": "~violates_prohibited_phrase(Response, Phrase)",
  "source": "domain_rule"
}

Negation

NSR-L supports two forms of negation:

Classical Negation (~)

Strong negation that asserts the opposite is true.
~alive(X)                      % X is definitely not alive
~ships_internationally.        % Definitely does not ship internationally

Negation as Failure (\+ or not)

Weak negation - true if the goal cannot be proven.
\+ found(X)                    % X was not found (maybe doesn't exist)
not(member(X, L))              % X is not provably in L

Difference

% Classical: We know X is not alive
~alive(zombie).

% NAF: We cannot prove X is alive (doesn't mean X is dead)
\+ alive(X).

Closed World Assumption

NAF follows the closed-world assumption: if something cannot be proven true, it is assumed false.
% Database only has:
employee(alice).
employee(bob).

% Query: Is carol an employee?
?- employee(carol).        % Fails (no fact)
?- \+ employee(carol).     % Succeeds (NAF)
?- ~employee(carol).       % Fails (no explicit negative fact)

Quantifiers

NSR-L supports first-order quantifiers.

Universal Quantification (forall)

forall X (human(X) -> mortal(X))
Equivalent rule form:
mortal(X) :- human(X).         % Implicitly forall X

Existential Quantification (exists)

exists X (person(X) & rich(X))
Can be expressed as:
has_rich_person :- person(X), rich(X).

Scoping

% All humans have some parent
forall X (human(X) -> exists Y parent(Y, X))

% There exists someone who is everyone's friend
exists X forall Y friend(X, Y)

Comments

Line Comments

% This is a line comment
human(socrates).  % Inline comment

Multi-line Comments (block style in code)

% ==============================================================================
% SECTION HEADER
% ==============================================================================
%
% Multiple lines of explanation.
% Each line starts with %.
%
% ==============================================================================

Complete Grammar

EBNF Grammar

program     = { statement } ;

statement   = fact | rule | query ;

fact        = expression "." ;

rule        = expression ":-" expression "." ;

query       = "?-" expression "." ;

expression  = disjunction ;

disjunction = conjunction { ( "|" | ";" ) conjunction } ;

conjunction = unary { ( "&" | "," ) unary } ;

unary       = "~" unary
            | "\+" unary
            | "not" "(" expression ")"
            | "forall" variable "(" expression ")"
            | "exists" variable "(" expression ")"
            | comparison ;

comparison  = arithmetic [ comp_op arithmetic ] ;

comp_op     = "=" | "\=" | "==" | "\=="
            | "<" | ">" | "=<" | "<=" | ">="
            | "=:=" | "=\=" | "is" ;

arithmetic  = term { ( "+" | "-" ) term } ;

term        = factor { ( "*" | "/" | "//" | "mod" | "%" ) factor } ;

factor      = [ "-" ] primary ;

primary     = atom [ "(" args ")" ]
            | variable
            | number
            | string
            | list
            | "(" expression ")"
            | "!" ;                         (* cut *)

args        = expression { "," expression } ;

list        = "[" [ list_items ] "]" ;

list_items  = expression { "," expression } [ "|" expression ] ;

atom        = lowercase { alphanumeric | "_" }
            | "'" { any_char } "'" ;

variable    = uppercase { alphanumeric | "_" }
            | "_" { alphanumeric | "_" } ;

number      = integer | float ;

integer     = [ "-" ] digit { digit } ;

float       = [ "-" ] digit { digit } "." digit { digit } [ exponent ] ;

exponent    = ( "e" | "E" ) [ "+" | "-" ] digit { digit } ;

string      = '"' { string_char } '"' ;

string_char = any_char_except_quote | '\\"' | "\\n" | "\\t" | "\\\\" ;

lowercase   = "a" | ... | "z" ;

uppercase   = "A" | ... | "Z" | "_" ;

digit       = "0" | ... | "9" ;

alphanumeric = lowercase | uppercase | digit ;

Examples

Example 1: Family Relationships

% Facts
parent(tom, mary).
parent(tom, jim).
parent(mary, ann).
parent(mary, pat).
parent(jim, bob).

male(tom).
male(jim).
male(bob).
male(pat).

female(mary).
female(ann).

% Rules
father(X, Y) :- parent(X, Y) & male(X).
mother(X, Y) :- parent(X, Y) & female(X).

grandparent(X, Z) :- parent(X, Y) & parent(Y, Z).

ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z) & ancestor(Z, Y).

sibling(X, Y) :- parent(P, X) & parent(P, Y) & X \= Y.

% Queries
?- father(tom, X).          % X = mary; X = jim
?- grandparent(tom, X).     % X = ann; X = pat; X = bob
?- sibling(mary, X).        % X = jim

Example 2: Customer Service Rules

% Products
product(wide_awake, eye_cream).
product(skin_glow_primer, primer).
has_mini(skin_glow_primer).
~has_mini(wide_awake).

% Prohibited phrases
prohibited_phrase("contact the manufacturer").
prohibited_phrase("30-40 pumps").

% Validation rules
response_invalid(R) :-
    prohibited_phrase(P) &
    contains_text(R, P).

response_valid(R) :-
    ~response_invalid(R) &
    ~contains_internal_reasoning(R).

% Escalation rules
requires_escalation(allergic_reaction).
requires_escalation(skin_irritation).

should_escalate(Topic) :-
    requires_escalation(Topic) &
    detected_topic(Topic).

% Topic detection
topic_detected(Inquiry, returns) :-
    contains_text(Inquiry, "return") |
    contains_text(Inquiry, "refund").

topic_detected(Inquiry, allergic_reaction) :-
    contains_text(Inquiry, "allergic") |
    contains_text(Inquiry, "rash").

Example 3: Arithmetic and Lists

% Sum of list
sum([], 0).
sum([H|T], S) :- sum(T, S1), S is H + S1.

% List length
len([], 0).
len([_|T], N) :- len(T, N1), N is N1 + 1.

% Maximum in list
max_list([X], X).
max_list([H|T], Max) :-
    max_list(T, MaxT),
    Max is max(H, MaxT).

% Filter positive numbers
filter_positive([], []).
filter_positive([H|T], [H|R]) :- H > 0, filter_positive(T, R).
filter_positive([H|T], R) :- H =< 0, filter_positive(T, R).

% Quicksort
quicksort([], []).
quicksort([H|T], Sorted) :-
    partition(H, T, Less, Greater),
    quicksort(Less, SortedLess),
    quicksort(Greater, SortedGreater),
    append(SortedLess, [H|SortedGreater], Sorted).

partition(_, [], [], []).
partition(Pivot, [H|T], [H|Less], Greater) :-
    H =< Pivot, partition(Pivot, T, Less, Greater).
partition(Pivot, [H|T], Less, [H|Greater]) :-
    H > Pivot, partition(Pivot, T, Less, Greater).

Example 4: Belief Revision and Constraints

% Initial beliefs with confidence
belief(human(socrates), 1.0).
belief(philosopher(socrates), 1.0).
belief(lived_in(socrates, athens), 0.95).

% Derived rules
mortal(X) :- human(X).
wise(X) :- philosopher(X).

% Hard constraint: Cannot live in two places at once
~valid_kb :-
    lived_in(X, Place1) &
    lived_in(X, Place2) &
    Place1 \= Place2 &
    overlapping_time(Place1, Place2).

% Soft constraint: Philosophers usually teach
expected(teaches(X)) :- philosopher(X).

% Query with explanation
?- mortal(socrates).
% Result: true
% Explanation: mortal(socrates) via human(socrates)

Error Messages

NSR-L provides helpful error messages with source locations:
error: Unexpected ')' after expression
  --> 3:15
   |
3  | mortal(X) :- human(X)).
   |              ^^^^^^^^^
   = help: Check for unbalanced parentheses

Common Errors

ErrorCauseFix
Singleton variable 'X'Variable used only onceRename to _X or use it
Unexpected tokenSyntax errorCheck operators and parentheses
Unbound variableVariable not instantiatedEnsure variable is bound before arithmetic
Division by zeroArithmetic errorAdd guard: Y \= 0
Wrong arityWrong number of argumentsCheck predicate definition

API Endpoints

EndpointMethodPurpose
/api/v1/nsrl/parsePOSTParse NSR-L code (syntax check)
/api/v1/nsrl/assertPOSTAdd facts/rules to knowledge base
/api/v1/nsrl/executePOSTExecute queries
/api/v1/nsrl/provePOSTProve entailment
/api/v1/nsrl/retractPOSTRemove facts/rules

Example API Call

curl -X POST "$BASE_URL/api/v1/nsrl/execute" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "human(socrates). mortal(X) :- human(X). ?- mortal(X).",
    "persist": false
  }'

Best Practices

1. Use Descriptive Predicate Names

% Good
customer_eligible_for_discount(C) :- ...

% Avoid
ced(C) :- ...

2. Handle Edge Cases

% Always handle empty list
process([], []).
process([H|T], Result) :- ...

3. Use Anonymous Variables

% When you don't need the value
has_child(X) :- parent(X, _).

4. Document with Comments

% Calculate final price after applying all discounts
% Precondition: base_price(Product, Price) exists
% Returns: Final price after discounts
final_price(Product, Final) :- ...

5. Avoid Infinite Recursion

% Bad: left recursion
ancestor(X, Y) :- ancestor(X, Z), parent(Z, Y).

% Good: base case first
ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).