Skip to content

Instantly share code, notes, and snippets.

@Qqwy
Created November 9, 2020 21:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Qqwy/c7033b37afb59ceac19331d073a7289c to your computer and use it in GitHub Desktop.
Save Qqwy/c7033b37afb59ceac19331d073a7289c to your computer and use it in GitHub Desktop.
A simple substitution (Caesar/Vernam) cypher, implemented in Prolog. Beginner code; can probably be significantly improved
-module(caesar_cipher, [
shift_plaintext_cyphertext/3,
shift_plainchar_cypherchar/3,
shift_plainletter_cypherletter/3
]).
:- use_module(library(reif)).
:- use_module(library(clpz)).
:- use_module(library(lists)).
:- use_module(library(between)).
% Performs a simple substitution cypher
% where all letters in `PlainText` are shifted `Shift` letters to become `CypherText`.
% This is a relation that can be used
% - To encrypt plaintext to cyphertext
% - To decrypt cyphertext to plaintext
% - To find out the shift between a given piece of PlainText and CypherText.
shift_plaintext_cyphertext(Shift, PlainText, CypherText) :-
maplist(shift_plainchar_cypherchar(Shift), PlainText, CypherText).
shift_plainchar_cypherchar(Shift, PlainChar, CypherChar) :-
% UGLY: Skip conversion if not yet ground.
% It would be nicer to use `when` but this is not (yet?) available in Scryer-Prolog.
% or is there even another way?
(nonvar(PlainChar) -> char_code(PlainChar, PlainCode) ; true),
(nonvar(CypherChar) -> char_code(CypherChar, CypherCode) ; true),
if_(PlainCode in 0'a..0'z #/\ CypherCode in 0'a..0'z,
shift_plainchar_cypherchar_(Shift, PlainCode, CypherCode, 0'a),
if_(PlainCode in 0'A..0'Z #/\ CypherCode in 0'A..0'Z,
shift_plainchar_cypherchar_(Shift, PlainCode, CypherCode, 0'A),
PlainCode = CypherCode % keep non-letters as-is.
)
),
% UGLY: Do the other conversion now, as we now know for certain that 3 of these 4 variables are ground.
char_code(PlainChar, PlainCode),
char_code(CypherChar, CypherCode).
% Wrapper for `shift_plainletter_cypherletter` that offsets
% the given character codes using `Offset`
% to convert between ASCII character codes and letters in the half-open range [0..26).
shift_plainchar_cypherchar_(Shift, PlainChar, CypherChar, Offset) :-
PlainLetter #= PlainChar - Offset,
CypherLetter #= CypherChar - Offset,
shift_plainletter_cypherletter(Shift, PlainLetter, CypherLetter).
% Expects letters as input in the half-open range [0..26)
shift_plainletter_cypherletter(Shift, PlainLetter, CypherLetter) :-
Shift in 0..26,
PlainLetter in 0..26,
CypherLetter in 0..26,
(PlainLetter + Shift) mod 26 #= CypherLetter mod 26.
% Reifiable variant of #/\
(#/\)(L, R, T) :-
L #/\ R #<==> B,
zo_t(B, T).
zo_t(0, false).
zo_t(1, true).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment