guest@BipbopPC:~/fun$ ./a.out < cool_site_input > mycoolsite.html

My Cool Site

A tiny markup language for building deliberately terrible 1998 web pages.

Flex · Bison · C · HTML · 2025

mycoolsite.html

There is a link at the bottom of the page through to its lamer sibling.

open standalone ↗

guest@BipbopPC:~/fun/coolsite$ cat NOTES

A second Flex and Bison compiler from the same era, this one aimed at HTML instead of OpenGL. A small markup language goes in, and a period-accurate personal home page comes out: tiled background images, centered flex columns, and text in whatever color seemed funniest at the time.

Two pages were built with it. This is the cool one. It links to the lame one, which links back, and neither is any good, which was the point.

SOURCE

What went in, and what turned it into the page above. These are the actual files, unedited.

[+] cool_site_input 54 lines markup for the cool site
COLOR LightSlateGray
REPEAT fish.png
FLEX transparent
IMG turtle.jpg
XELF
FLEX MistyRose
TEXT[red] WELCOME!
BREAK
LARGE Welcome to My Cool Site!
BREAK
TEXT You have *chosen*, or been chosen, to relocate to one of our finest web centers.
TEXT I thought so much of my cool site that I elected to establish my administration here.
TEXT In the console so thoughtfully provided by Mozilla.
BREAK
TEXT I have been proud to call my cool site my home.
TEXT SO, whether you are here to stay... or passing through on your way to parts unknown...
BREAK
BOLD WELCOME... to My Cool Site...
BREAK
BOLD It's cooler here.
BREAK
BREAK
BREAK
TEXT Here is my other LAME site
LINK mylamesite.html
BREAK
BREAK
TEXT Here are some other cool sites:
LINK https://funnyjunk.com/
LINK https://zombo.com/
LINK https://youareanidiot.cc/
LINK https://cat-bounce.com/
LINK https://www.gnu.org/
BREAK
TEXT PRETTY COOL!?! RIGHT!????1/
TEXT HERES SOME SKELETON
IMG skele.gif
IMG skele2.gif
XELF
BEGIN SCRIPT

    LET total_loops = 0
    LET loops = 0
    PRINT "first time around"
    DO WHILE loops < 3
        LET loops = loops + 1
        PRINT "lets go!"
    LOOP
    LET total_loops = total_loops + 1

    IF total_loops < 5 THEN
        PRINT "AROUND AGAIN!"
        GOTO 2
    ENDIF
[+] mylamesite_input 164 lines markup for the lame site
COLOR green
REPEAT sad.png
FLEX red
TEXT weldceom to my laem site
IMG lamo.jpg
TEXT[blue] this is The lAme site you ShouDL go Olook at the COOL oen
LINK mycoolsite.html
TEXT my COLO SITE is THerE^^^^^^^^^
TEXT I guEs you watn me to do SomMEthingG cause you worKEdso hard to get to thIS wesbite
BOLD LOOK IM BOLD NOW
LARGE but I can also make small text to
TEXT[gold] sory I Meaned BIG tex
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT HEY YOU!
TEXT DOWN HERE!
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT DOWN HERE!
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT KEEP GOING!
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT YOU'RE ALMOST THERE!
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT THIS IS NOT THE END
IMG otter.gif
TEXT KEEP GOING!
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT YOU THOUGHT YOU WERE DONE, HUH? NOPE!
IMG hello.gif
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT Does this website end? I don't think it does
IMG notthere.png
TEXT It's probably just infinite... you should give up now
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
BREAK
TEXT JUST KIDDING THIS IS THE END!
IMG final.webp
XELF
BEGIN SCRIPT

    PRINT "script"
[+] basic.l 57 lines Flex lexer
%option noyywrap
%option yylineno

%{
#include "basic.tab.h"
#include <string.h>
%}

ID [A-Za-z_][A-Za-z0-9_]*
NUM [0-9]+
STRING \"[A-za-z0-9_ '?''!''.'',']*\"

%%
"BEGIN SCRIPT"      return BEGINS;
"END SCRIPT"        return ENDS;
"LET"               return LET;
"PRINT"             return PRINT;
"IF"                return IF;
"THEN"              return THEN;
"ELSE"              return ELSE;
"ENDIF"             return ENDIF;
"LOOP"              return LOOP;
"GOTO"              return GOTO;
"BREAK"             return BREAK;
"XELF"              return XELF;

"DO WHILE "         return DOWHILE;
"REM ".*            return REM;

"FLEX ".*/\n        { yylval.s = strdup(yytext+5); return FLEX; }
"COLOR ".*/\n       { yylval.s = strdup(yytext+6); return COLOR; }
"TEXT ".*/\n        { yylval.s = strdup(yytext+5); return TEXT; }
"IMG ".*/\n         { yylval.s = strdup(yytext+4); return IMG; }
"LINK ".*/\n        { yylval.s = strdup(yytext+5); return LINK; }
"BOLD ".*/\n        { yylval.s = strdup(yytext+5); return BOLD; }
"LARGE ".*/\n       { yylval.s = strdup(yytext+6); return LARGE; }
"REPEAT ".*/\n      { yylval.s = strdup(yytext+7); return REPEAT; }
"TEXT"\[.*\].*/\n   {
                        yylval.ss = (char**) malloc(2*sizeof(char*));
                        int color_bound = (int)(strrchr(yytext, ']') - yytext);
                        yytext[color_bound] = '\0';
                        yylval.ss[0] = strdup(yytext + 5);
                        yylval.ss[1] = strdup(yytext + color_bound + 1);
                        return COLOR_TEXT;
                    }

{STRING}       { yylval.s = strdup(yytext); return STRING; }
{ID}           { yylval.s = strdup(yytext); return ATOM; }
{NUM}          { yylval.i = atoi(yytext); return NUM; }


"="            return '=';
"\n"           return EOL;
[ \t\r]        ;
.              return yytext[0];

%%
[+] basic.y 203 lines Bison grammar and HTML codegen
%define parse.error detailed

%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>

extern int yylineno;

int yylex(void);
void yyerror(const char *s);

char** line_array;
int current_line = 0;

char style_line[256];

void clp() {
  current_line++;
}

%}

%union {
  char *s;
  char **ss;
  int i;
}

%token LET PRINT IF THEN ELSE ENDIF EOL BEGINS ENDS DOWHILE LOOP GOTO XELF BREAK
%token <s> ATOM COLOR TEXT IMG BOLD PRETTY_TEXT REPEAT STRING REM LARGE LINK FLEX
%token <ss> COLOR_TEXT
%token <i> NUM
%type <s> expr if_cond
%type <i> num_expr

%left '<' '>' '='
%left '+' '-'
%left '*' '/'

%%

script
  :  { printf("<head><style>p{margin-top: 0; margin-bottom: 0;}</style></head>"); }
     html_body
     BEGINS EOL
     {
        printf("<body style=\"%s\">", style_line);
        for (int i = 0; i < current_line; i++) {
          printf("%s", line_array[i]);
          free(line_array[i]);
        }
        current_line = 0;
        printf("<script>\n");
     }
     program {
        for (int i = 0; i < current_line; i++) {
          printf("%s", line_array[i]);
          free(line_array[i]);
        }
        printf("</script>\n</body>");
     }
  | EOL
  ;

html_body
  : html required html
  ;

required
  : COLOR {
            char* color_string;
            asprintf(&color_string, "background-color:%s;", $1);
            strcat(style_line, color_string);
            free($1);
            free(color_string);
          }
    EOL
  ;

html
  :
  | html html_elements
  ;

html_elements
  : TEXT EOL        { asprintf(&line_array[current_line], "<p>%s</p>",$1); clp(); free($1); }
  | BREAK EOL       { asprintf(&line_array[current_line], "<p><br></p>"); clp(); }
  | IMG EOL         { asprintf(&line_array[current_line], "<img src=\"%s\"><br>\n", $1); clp(); free($1); }
  | LINK EOL        { asprintf(&line_array[current_line], "<a href=\"%s\">%s</a><br>\n", $1, $1); clp(); free($1); }
  | BOLD EOL        { asprintf(&line_array[current_line], "<strong>%s</strong><br>\n", $1); clp(); free($1); }
  | LARGE EOL       { asprintf(&line_array[current_line], "<p style=\"font-size: 24px;\">%s</p>\n", $1); clp(); free($1); }
  | COLOR_TEXT EOL  { asprintf(&line_array[current_line], "<p style=\"color: %s;\">%s</p>", $1[0], $1[1]); free($1[0]); free($1[1]); clp(); }
  | REPEAT EOL
                    {
                      char* repeat_string;
                      asprintf(&repeat_string, "background-image: url('%s'); background-repeat: repeat;", $1);
                      strcat(style_line, repeat_string);
                      free($1);
                      free(repeat_string);
                    }
  | FLEX EOL { asprintf(&line_array[current_line],
          "<div style=\"display:flex;flex-direction:column; align-items:center;justify-content:center; height:fit-content;margin: auto;width:40\%;background-color:%s;\">\n", $1); clp(); }
    html XELF EOL
      { asprintf(&line_array[current_line], "</div>\n"); clp(); }
  ;

program
  : /* empty */ { current_line = 0; }
  | program line
  ;

line
  : LET ATOM '=' expr EOL
      { asprintf(&line_array[current_line], "var %s = %s;\n", $2, $4); free($2); free($4); clp(); }
  | PRINT expr EOL
      { asprintf(&line_array[current_line], "console.log(%s);\n", $2); free($2); clp(); }
  | PRINT STRING EOL
      { asprintf(&line_array[current_line], "console.log(%s);\n", $2); free($2); clp(); }
  | REM EOL { line_array[current_line] = strdup(""); clp(); }
  | if_stmt
  | do_while_stmt
  /* GOTO is broken by ELSE statements... but I don't care anymore */
  | GOTO NUM EOL
    {
      char* old_ptr = line_array[$2];
      asprintf(&line_array[$2], "while(1) { %s", old_ptr);
      free(old_ptr);
      asprintf(&line_array[current_line], "continue;\n} break;\n", $2); clp();
    }
  | EOL
      { line_array[current_line] = strdup("\n"); clp(); }
  ;

if_stmt
  : IF if_cond THEN EOL
      { asprintf(&line_array[current_line], "if (%s) {\n\t", $2); free($2); clp(); }
    block else_clause ENDIF EOL
      { asprintf(&line_array[current_line], "}\n"); clp(); }
  ;

else_clause
  :
  | ELSE EOL
      { asprintf(&line_array[current_line], "} else {\n"); clp(); }
      block
  ;

do_while_stmt
  : DOWHILE if_cond EOL
      { asprintf(&line_array[current_line], "while (%s) {\n", $2); free($2); clp(); }
    block LOOP EOL
      { asprintf(&line_array[current_line], "}\n"); clp(); }
  ;

block
  : /* empty */
  | block line
  ;

if_cond
  : expr '<' expr { asprintf(&$$, "%s < %s", $1, $3); free($1); free($3); }
  | expr '>' expr { asprintf(&$$, "%s > %s", $1, $3); free($1); free($3); }
  | expr '=' expr { asprintf(&$$, "%s == %s", $1, $3); free($1); free($3); }
  | expr          { $$ = $1; }
  ;

expr
  : ATOM
      { $$ = $1; }
  | num_expr
      { asprintf(&$$, "%d", $1); }
  | '(' expr ')'
      { asprintf(&$$, "(%s)", $2); }
  | expr '+' expr { asprintf(&$$, "%s + %s", $1, $3); free($1); free($3); }
  | expr '-' expr { asprintf(&$$, "%s - %s", $1, $3); free($1); free($3); }
  | expr '*' expr { asprintf(&$$, "%s * %s", $1, $3); free($1); free($3); }
  | expr '/' expr { asprintf(&$$, "%s / %s", $1, $3); free($1); free($3); }
  ;

num_expr
  : NUM            { $$ = $1; }
  /*
  | NUM '+' NUM    { $$ = $1 + $3; }
  | NUM '-' NUM    { $$ = $1 - $3; }
  | NUM '*' NUM    { $$ = $1 * $3; }
  | NUM '/' NUM    { $$ = $1 / $3; }
  */
  ;
%%

int main(void) {
    line_array = calloc(INT16_MAX, sizeof(char*));
    style_line[0] = '\0';
    yyparse();
    return 0;
}

void yyerror(const char *s) {
    fprintf(stderr, "Error on line %d: %s\n", yylineno, s);
}
Christopher Gemperle / synackfinack Chico, CA