Created
November 9, 2020 21:53
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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