-
-
Save Nicd/3bdeb9ec01ca333ceade5323f50d5425 to your computer and use it in GitHub Desktop.
GTLT was a 2D Brainfuck-inspired esoteric language I came up with ages ago for fun
This file contains 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
EThe answer to life, the | |
universe and everything | |
§v >}}++++++++++!{+!v | |
>+++++]}++++++v>--!Q - | |
- - ! - | |
^< ^{++++++<^-------< |
This file contains 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
++++++++++} | |
+++++++++}+++++++++ | |
> o | |
{+++++++++++++++++ | |
+++++++++++++++++ | |
++++++++++++++ } | |
+++++++++++++++++ | |
+++++++++++++++++ | |
++++++++++++++ {v | |
^ < +{+} < | |
-> E01 bottle of§ v | |
- E | |
} | |
v}-{< b | |
+v {{< O}}<e | |
^(>!}!E bottles §ve | |
ov v§t no reeb foE<r | |
-} >Ehe wall, §{!}v | |
-- v§fo selttob E!<o | |
-- >E beer. Take §vn | |
-- v§ap ,nwod enoE< | |
->^>Ess it around§vt | |
-v+++++++++{-< § | |
- v}-{( E | |
->{-}} > v h | |
- {} e | |
- v§ E!}!{<<^§ ,E< | |
- >Ebottles of b§vw | |
- v§w eht no reeE<a | |
- >Eall.§--------vl | |
- v--------------<l | |
- >--------------v, | |
- v---{----------< | |
- >--------------v0 | |
^--------}!{------<§ | |
v§reeb fo elttob 1E< | |
>E. Take it down, pa | |
ss it around, no bot | |
tles of beer on the | |
wall.§{{!ENo bottles | |
of beer on the wall | |
, no bottles of beer | |
. Go to the store an | |
d buy some more, 99 | |
bottles of beer on t | |
he wall.§Q |
This file contains 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
/* GTLT Parser 1.0 | |
* C++ -version | |
* Compiles on Mac OS X 10.4.11 | |
* with g++ 4.0.1 | |
* | |
* All code copyright Nicd 2007 | |
*/ | |
#include <iostream> | |
#include <cstdlib> | |
#include <string> | |
#include <vector> | |
#include <fstream> | |
using namespace std; | |
// Random number between lowest and highest | |
int random_between(int lowest, int highest); | |
// Randomly return either of the two arguments | |
int random_from(int a, int b); | |
//! \return modified string s with spaces trimmed from left | |
std::string& triml(std::string& s); | |
//! \return modified string s with spaces trimmed from right | |
std::string& trimr(std::string& s); | |
//! \return modified string s with spaces trimmed from edges | |
std::string& trim(std::string& s); | |
int main(int argc, char *argv[]) { | |
const int START_STATE = 0; // Starting state for all memory cells | |
// Seed random generator | |
srand((unsigned)time(0)); | |
char *file; | |
if(argc != 2) { | |
cerr << "Wrong parameter count!" << endl; | |
cerr << "Usage: gtlt /path/to/source.g" << endl; | |
return EXIT_SUCCESS; | |
} | |
else { | |
file = argv[1]; | |
} | |
// Master vector holding all the line strings | |
vector<string> source; | |
string temp; | |
ifstream h; | |
h.open(file, ios::in); | |
if(!h) { | |
cerr << "Specified file does not exist!" << endl; | |
return EXIT_FAILURE; | |
} | |
// Load lines of file to a vector, like file() in PHP | |
while(getline(h, temp)) { | |
source.push_back(temp); | |
} | |
// Determine if line lenghts are ok (source is rectangular) | |
int test_length = 0; | |
vector<string>::iterator i; | |
for(i = source.begin(); i != source.end(); ++i) { | |
temp = *i; | |
if(test_length == 0) { | |
test_length = temp.length(); | |
} | |
else if(test_length != temp.length()) { | |
cerr << "Source file is not rectangular!" << endl; | |
h.close(); | |
return EXIT_FAILURE; | |
} | |
} | |
if(test_length == 0) { | |
cerr << "Source file seems to be empty." << endl; | |
h.close(); | |
return EXIT_FAILURE; | |
} | |
h.close(); // We don't need the file anymore | |
// Initialize basic variables | |
string input = ""; // User input | |
char key = '0'; // User-entered char | |
char current_source = 0; // Current source value | |
int current_memory = 0; // Current memory value | |
bool echo = false; // Are we echoing? | |
bool verbose = false; // Are we verbose? | |
bool end_of_input = false; // Nothing more in input buffer? | |
int x = test_length; // Source x-size | |
int y = source.size(); // Source y-size | |
int sx = -1; // Source cursor x-coordinate | |
int sy = 0; // Source cursor y-coordinate | |
int mx = 0; // Memory cursor x-coordinate | |
int my = 0; // Memory cursor y-coordinate | |
int dir = 2; // Running direction: | |
// 1 = up | |
// 2 = right | |
// 3 = down | |
// 4 = left | |
// Naturally, we start going right | |
// Let's create the memory too! | |
vector< vector< int > > memory; | |
vector<int> temp_vector; | |
int r = 0; | |
temp_vector.reserve(x); | |
for(r = 0; r < x; ++r) { | |
temp_vector.push_back(START_STATE); | |
} | |
memory.reserve(y); | |
for(r = 0; r < y; ++r) { | |
memory.push_back(temp_vector); | |
} | |
// Enter main parsing loop | |
while(true) { | |
// Ignore user input when not asking for it | |
// cin.ignore(1); | |
// Move source cursor | |
switch(dir) { | |
case 1: | |
sy--; | |
break; | |
case 2: | |
sx++; | |
break; | |
case 3: | |
sy++; | |
break; | |
case 4: | |
sx--; | |
break; | |
default: | |
dir = 1; | |
sy--; | |
break; | |
} | |
// Check if out of bounds | |
if(sx < 0) { | |
sx = x - 1; | |
sy--; | |
if(sy < 0) { | |
sx = x - 1; | |
sy = y - 1; | |
} | |
} | |
if(sx > (x - 1)) { | |
sx = 0; | |
sy++; | |
if(sy > (y - 1)) { | |
break; | |
} | |
} | |
if(sy < 0) { | |
sx--; | |
sy = y - 1; | |
if(sx < 0) { | |
sx = x - 1; | |
sy = y - 1; | |
} | |
} | |
if(sy > (y - 1)) { | |
sx++; | |
sy = 0; | |
if(sx > (x - 1)) { | |
break; | |
} | |
} | |
// Get current source & memory values | |
current_source = static_cast<char>(source.at(sy).at(sx)); | |
current_memory = memory.at(my).at(mx); | |
// Check current cell for commands | |
if(current_source == '§') { | |
echo = false; | |
continue; | |
} | |
// If in echo mode, write to stdout | |
if(echo) { | |
cout << current_source; | |
} | |
else { | |
switch(current_source) { | |
// Unconditial flow controls | |
case '<': | |
dir = 4; | |
break; | |
case '>': | |
dir = 2; | |
break; | |
case '^': | |
dir = 1; | |
break; | |
case 'v': | |
dir = 3; | |
break; | |
// Conditional flow controls | |
case '[': | |
if(current_memory != 0) { | |
dir = 4; | |
} | |
break; | |
case ']': | |
if(current_memory != 0) { | |
dir = 2; | |
} | |
break; | |
case '(': | |
if(current_memory == 0) { | |
dir = 4; | |
} | |
break; | |
case ')': | |
if(current_memory == 0) { | |
dir = 2; | |
} | |
break; | |
case 'O': | |
if(current_memory != 0) { | |
dir++; | |
} | |
break; | |
case 'o': | |
if(current_memory == 0) { | |
dir++; | |
} | |
break; | |
// Other flow controls | |
case '=': | |
dir = random_from(2, 4); | |
break; | |
case '|': | |
dir = random_from(1, 3); | |
break; | |
case '@': | |
dir = random_between(1, 4); | |
break; | |
// Memory controls | |
case '{': | |
mx--; | |
if(mx < 0) { | |
mx = x - 1; | |
my--; | |
if(my < 0) { | |
my = y - 1; | |
} | |
} | |
break; | |
case '}': | |
mx++; | |
if(mx > (x - 1)) { | |
mx = 0; | |
my++; | |
if(my > (y - 1)) { | |
my = 0; | |
} | |
} | |
break; | |
case 'A': | |
my--; | |
if(my < 0) { | |
mx--; | |
my = y - 1; | |
if(mx < 0) { | |
mx = x - 1; | |
} | |
} | |
break; | |
case 'V': | |
my++; | |
if(my > (y - 1)) { | |
mx++; | |
my = 0; | |
if(mx > (x - 1)) { | |
mx = 0; | |
} | |
} | |
break; | |
case '+': | |
memory[my][mx]++; | |
break; | |
case '-': | |
memory[my][mx]--; | |
break; | |
case '0': | |
memory[my][mx] = 0; | |
break; | |
// I/O controls | |
case '?': | |
// if(end_of_input) { | |
// memory[my][mx] = 0; // End of input | |
// end_of_input = false; | |
// break; | |
//} | |
if(input.length() != 0) { | |
memory[my][mx] = static_cast<int>(static_cast<char>(input.at(0))); | |
input.erase(0, 1); | |
//if(input.length() == 0) { | |
// end_of_input = true; | |
//} | |
} | |
else { | |
getline(cin, input); | |
input = trimr(input); | |
if(input.length() == 0) { | |
memory[my][mx] = 0; | |
} | |
else { | |
memory[my][mx] = static_cast<int>(static_cast<char>(input.at(0))); | |
input.erase(0, 1); | |
//if(input.length() == 0) { | |
// end_of_input = true; | |
//} | |
} | |
} | |
break; | |
case '!': | |
cout << static_cast<char>(current_memory); | |
break; | |
case ':': | |
// Enter raw mode for single character input | |
// This would use getch(); on Windows | |
system("stty raw"); | |
cin.get(key); | |
if(static_cast<int>(key) == 10 || static_cast<int>(key) == 13) { | |
memory[my][mx] = 0; | |
} | |
else { | |
memory[my][mx] = static_cast<int>(key); | |
} | |
system("stty cooked"); | |
break; | |
case 'E': | |
echo = true; | |
break; | |
// § handled elsewhere | |
// Other controls | |
case 'Q': | |
cout << endl; | |
return EXIT_SUCCESS; | |
case 'R': | |
sx = random_between(0, (x - 1)); | |
sy = random_between(0, (y - 1)); | |
break; | |
case 'C': | |
memory.clear(); | |
for(r = 0; r < y; ++r) { | |
memory.push_back(temp_vector); | |
} | |
break; | |
case 'S': | |
for(i = source.begin(); i != source.end(); ++i) { | |
cout << *i << endl; | |
} | |
break; | |
case 'H': | |
cout << "Hello, world!"; | |
break; | |
case 'D': | |
cout << "Source position: " << sx << ", " << sy << endl; | |
cout << "Memory position: " << mx << ", " << my << endl; | |
cout << "Memory cell value: " << current_memory << endl; | |
cout << "Input buffer: " << input << endl; | |
break; | |
case 'T': | |
verbose = true; | |
break; | |
} | |
} | |
// Echo stuff if we are being verbose | |
if(verbose) { | |
cout << "Source position: " << sx << ", " << sy << endl; | |
cout << "Memory position: " << mx << ", " << my << endl; | |
cout << "Current source value: " << current_source << endl; | |
cout << "Memory cell value: " << current_memory << endl; | |
cout << "Input buffer: " << input << endl; | |
} | |
} | |
cout << endl; | |
return EXIT_SUCCESS; | |
} | |
int random_between(int lowest, int highest) { | |
int random; | |
int range = (highest - lowest) + 1; | |
random = lowest + int(range * rand() / (RAND_MAX + 1.0)); | |
return random; | |
} | |
int random_from(int a, int b) { | |
if(random_between(1, 2) == 1) { | |
return a; | |
} | |
else { | |
return b; | |
} | |
} | |
//! \return modified string ``s'' with spaces trimmed from left | |
std::string& triml(std::string& s) { | |
int pos(0); | |
for ( ; s[pos]==' ' || s[pos]=='\t'; ++pos ); | |
s.erase(0, pos); | |
return s; | |
} | |
//! \return modified string ``s'' with spaces trimmed from right | |
std::string& trimr(std::string& s) { | |
int pos(s.size()); | |
for ( ; pos && s[pos-1]==' ' || s[pos]=='\t'; --pos ); | |
s.erase(pos, s.size()-pos); | |
return s; | |
} | |
//! \return modified string ``s'' with spaces trimmed from edges | |
std::string& trim(std::string& s) { | |
return triml(trimr(s)); | |
} |
This file contains 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
GTLT spesifikaatio 1.2 (20.2.2008) | |
=== | |
GTLT (c) Nicd 2006 - nykyisyys. | |
GTLT on esoteerinen (turha) ohjelmointikieli, jonka perimmäinen tarkoitus on olla | |
mahdoton lukea ja vielä mahdottomampi kirjoittaa. Sitä voi ajaa komentoriviltä | |
sitä varten kehitetyllä PHP-parserilla, tyyliin tähän (nyt saatavilla myös C++ | |
-parseri): | |
>php parser.php 99bottles.g | |
Ohjelmointikieli perustuu mahtavaan Brainfuck -kieleen ja sitä voi ajatella ikään | |
kuin kaksiulotteisena Brainfuckina. Komentoja on kuitenkin enemmän ja toiminta | |
on joissain kohdin erilaista. | |
Ohjelmat | |
--- | |
Jokainen ohjelma on suorakulmion muotoinen, ja sen kaikki neljä sivua ovat tasaisia. | |
Kaikki komennot ovat yhden kirjaimen mittaisia, tunnistamattomat kirjaimet | |
ohitetaan. Ohjelman suoritus alkaa vasemmasta ylänurkasta ja se lähtee oikealle, | |
ellei suuntakomentoihin törmätä. Ohjelman suoritus loppuu lopetuskomentoon tai | |
kun suoritus kulkee oikean alanurkan viimeisen komennon ohi. | |
GTLT-ohjelmat tallennetaan tiedostopäätteellä .g, tavalliseen tekstitiedostomuotoon. | |
Rivinvaihdot poistetaan eikä niitä katsota komennoiksi, mutta välilyönnit ja | |
sarkaimet (tabulator) säilyvät. Huomionarvoista on että sarkain tulkitaan vain | |
yhdeksi merkiksi, niin kuin se onkin. | |
Muisti | |
--- | |
Jokaisella ohjelmalla on oma muistitaulukko, joka luodaan automaattisesti | |
ohjelman suorituksen alkaessa. Taulukko on täsmälleen saman kokoinen kuin | |
ohjelmatiedosto itse, tauluja on siis yhtä paljon kuin ohjelmassa merkkejä. Näin | |
ollen suuret ohjelmat kuluttavat automaattisesti paljon enemmän muistia. | |
Kaikkissa muistitauluissa on aluksi 0. Muistiin voi säilöä vain numeroita, mutta | |
luvulla taulussa ei ole ylä- tai alarajaa, muuta kuin mitä parseriohjelma tai sen | |
ohjelmointikieli asettaa. | |
Ohjelma ei liiku muistissa samaan tapaan kuin suoritettaessa lähdekoodia, vaan | |
muistissa liikutaan muistikomennoilla yksi taulu kerrallaan. | |
Ohjelmassa liikkuminen | |
--- | |
Ohjelman suorituksen alkaessa parseriohjelma lähtee lukemaan tiedostoa | |
vasemmasta ylänurkasta normaaliin lukusuuntaan, oikealle. Kun rivin reuna | |
kohdataan, siirrytään seuraavalle riville, jälleen alkaen vasemmasta reunasta. | |
Lukusuuntaa voi kuitenkin itse muuttaa suuntakomennoilla, ja se voi olla mikä | |
tahansa neljästä perussuunnasta: ylös, alas, oikealle tai vasemmalle. | |
Kohdattaessa reuna, siirrytään aina suunnan mukaisesti seuraavalle (pysty)riville, | |
ja aloitetaan lukeminen päinvastaisesta reunasta. Esimerkiksi ylitettäessä alareuna, | |
lukeminen siirtyy seuraavaan pystyriviin oikealle, yläreunaan, kun taas ylitettäessä | |
yläreuna, lukeminen siirtyy seuraavaan pystyriviin _vasemmalle_, alareunaan. | |
Jos siirrytään jonkin ohjelman nurkan yli, eikä löydy enää seuraavaa riviä mihin | |
siirtyä, ohjelman suoritus jatkuu päinvastaisesta nurkasta. Tässä oikea alanurkka | |
on poikkeus, sen yli siirryttäessä ohjelman suoritus loppuu, sillä se tulkitaan | |
tiedoston lopuksi. | |
Komentolistaus | |
--- | |
Suuntakomennot | |
< - Vaihda kulkusuunta vasemmalle | |
> - ... oikealle | |
^ - ... ylös | |
v - ... alas | |
Ehdolliset suuntakomennot (jos ehto ei täyty, kulku jatkuu aikaisempaan suuntaan) | |
[ - Jos tämänhetkinen muistitaulu ei ole nolla, vaihda suunta vasemmalle | |
] - ... oikealle | |
( - Jos tämänhetkinen muistitaulu on nolla, vaihda suunta vasemmalle | |
) - ... oikealle | |
O - Jos tämänhetkinen muistitaulu ei ole nolla, vaihda suunta 90 astetta | |
myötäpäivään | |
o - Jos tämänhetkinen muistitaulu on nolla, vaihda suunta 90 astetta myötäpäivään | |
Muita suuntakomentoja | |
= - Vaihda suunta joko oikealle tai vasemmalle satunnaisesti | |
| - ... ylös tai alas | |
@ - Vaihda suunta mihin tahansa suuntaan satunnaisesti | |
Muistikomennot | |
{ - Siirry seuraavaan muistitauluun vasemmalla | |
} - ... oikealla | |
A - ... ylhäällä | |
V - ... alhaalla | |
+ - Lisää yksi tämänhetkisen muistitaulun arvoon | |
- - Vähennä yksi tämänhetkisen muistitaulun arvosta | |
0 - Aseta tämänhetkisen muistitaulun arvoksi nolla | |
I/O -komennot | |
? - Ota syöte ja lisää sen ensimmäisen kirjaimen ASCII-arvo | |
tämänhetkiseen muistitauluun. Jos syötteessä on edellisestä operaatiosta jäljelle | |
jääneitä kirjaimia, ota niistä ensimmäinen ja poista se syötteestä (ei toimi PHP- | |
parserilla.) | |
: - Ota käyttäjältä yhden kirjaimen mittainen syöte ilman, että käyttäjän tarvitsee | |
painaa rivinvaihtoa. Suoritus jatkuu siis heti kun mitä tahansa tunnistettavaa | |
näppäintä on painettu. Jos painetaan suoraan rivinvaihtoa, se tulkitaan arvoksi | |
0. (Ei toimi PHP-parserilla.) | |
! - Tulosta tämänhetkisen muistitaulun sisältö ASCII-taulukon mukaisesti | |
E - Jatka kulkusuuntaan ja tulosta kaikki vastaan tulevat kirjaimet komentoja | |
suorittamatta kunnes tavataan tulostuksen lopetuskomento tai tiedoston loppu | |
§ - Tulostuksen lopetuskomento | |
Muut komennot | |
Q - Lopeta ohjelman suoritus välittömästi | |
R - Hyppää satunnaiseen kohtaan ohjelman lähdekoodissa ja jatka suoritusta | |
kulkusuuntaan | |
C - Tyhjennä kaikki muistitaulut (aseta nollaksi) | |
S - Tulosta ohjelman lähdekoodi | |
D - Tulosta debug-tietoa | |
T - Puhelias (talkative, verbose); tulosta debug-tietoa jokaisen ruudun suorituksen | |
jälkeen | |
H - Tulosta "Hello, world!" | |
Esimerkkejä | |
--- | |
>Q | |
+++++]}+++++v | |
^ -{< | |
Asettaa muistitauluun, kohtaan (0,2) arvon 25. (Kertominen) | |
EThe answer to life, the | |
universe and everything | |
§v >}}++++++++++!{+!v | |
>+++++]}++++++v>--!Q - | |
- - ! - | |
^< ^{++++++<^-------< | |
Tulostaa: | |
The answer to life, the universe and everything | |
=42 | |
++++++++++} | |
+++++++++}+++++++++ | |
> o | |
{+++++++++++++++++ | |
+++++++++++++++++ | |
++++++++++++++ } | |
+++++++++++++++++ | |
+++++++++++++++++ | |
++++++++++++++ {v | |
^ < +{+} < | |
-> E01 bottle of§ v | |
- E | |
} | |
v}-{< b | |
+v {{< O}}<e | |
^(>!}!E bottles §ve | |
ov v§t no reeb foE<r | |
-} >Ehe wall, §{!}v | |
-- v§fo selttob E!<o | |
-- >E beer. Take §vn | |
-- v§ap ,nwod enoE< | |
->^>Ess it around§vt | |
-v+++++++++{-< § | |
- v}-{( E | |
->{-}} > v h | |
- {} e | |
- v§ E!}!{<<^§ ,E< | |
- >Ebottles of b§vw | |
- v§w eht no reeE<a | |
- >Eall.§--------vl | |
- v--------------<l | |
- >--------------v, | |
- v---{----------< | |
- >--------------v0 | |
^--------}!{------<§ | |
v§reeb fo elttob 1E< | |
>E. Take it down, pa | |
ss it around, no bot | |
tles of beer on the | |
wall.§{{!ENo bottles | |
of beer on the wall | |
, no bottles of beer | |
. Go to the store an | |
d buy some more, 99 | |
bottles of beer on t | |
he wall.§Q | |
Tulostaa 99 pulloa olutta -laulun sanat (99-bottles-of-beer.net). | |
H | |
Tulostaa "Hello, world!" | |
PHP-parseri on löydettävissä osoitteesta http://stuff.nytsoi.net/gtlt/parser.php | |
Sitä ajetaan komentoriviltä ja se ottaa yhden argumentin, GTLT-tiedoston osoitteen. | |
GTLT:tä voi ajaa myös uudella C++ -parserilla (suositeltavaa), joka löytyy samasta | |
osoitteesta nimellä gtlt.cc. Se kääntyy muutoksitta ainakin tiedostossa mainituilla | |
käyttöjärjestelmillä. |
This file contains 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
H |
This file contains 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
v Kokonaislukujen jakolasku | |
>++++++++++}EAnna jaettava: §?}EAnna jakaja: §?v | |
v < | |
>++++++++ v> v | |
>}++++++++++]-{----{----v>]-{-{-v | |
^ }}< ^ }}< | |
v < | |
>VV v>AA} v>VV v>AA{ v | |
>{{]-V+V+v>]-AA+v>]-V+V+v>]-AA+v | |
^ AA< ^ VV< ^ AA< ^ VV< | |
v{{0}} < | |
> v | |
>]}}}+v > > v | |
{ >{{]{-}-} +v | |
{ ^ }o{{<0 >0 v | |
>}]-{+v | |
^ }< | |
^ < | |
v < | |
>A o v >AA v | |
>}V ]-V+AA-v>}}+{{0v>VV ]-AA+v | |
^ V< ^ VV< | |
v < < | |
>++++++++ v> v | |
>}}}++++++++++]-{++++{{++++v>]-{+{{+v | |
^ }}}< ^ }}}< | |
v < | |
>{{{{{!}}}}ETulos: §-!E, jää yli §{{!E.§Q |
This file contains 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
v Yksikaksi.g, mesierkki | |
>+++++++++++v>}}+++v | |
>{+!E, §}v>+ ]-}++v+ | |
ov -^ <^ {++<+ | |
^ < < | |
>EGTLT:llä koodaamme si | |
is!§Q |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment