/* to compile: cc main.c -o main
$pow_ { (base mul power -> base mul' power-1)
dup (base mul pow pow)
0 gt if (base mul pow)
dec (base mul pow-1)
swp (base pow-1 mul)
2 peek (base pow-1 mul base)
mul (base pow-1 mul')
swp (base mul' pow-1)
pow_
end
}
$pow { ( base power -> base^power)
1 swp pow_
pop swp pop
} (base power -> base^power)
2 10 pow
typedef uint32_t u32;
typedef int32_t i32;
typedef int32_t b32;
typedef enum
{
PARSING_ANY,
PARSING_DECIMAL,
PARSING_HEX,
PARSING_IDENTIFIER,
PARSING_COMMENT
} parser_state;
typedef struct
{
u32 Count;
char *Start;
} string_slice;
X(add) /* (a b -> a+b) */ \
X(sub) /* (a b -> a-b) */ \
X(mul) /* (a b -> a*b) */ \
X(div) /* (a b -> a/b) */ \
X(mod) /* (a b -> a%b) */ \
X(or) /* (a b -> a||b) */ \
X(xor) /* (a b -> a^b) */ \
X(and) /* (a b -> a&&b) */ \
X(not) /* (a -> !a) */ \
X(bor) /* (a b -> a|b) */ \
X(swp) /* (a b -> b a) */ \
X(band) /* (a b -> a&b) */ \
X(bnot) /* (a -> ~a) */ \
X(eq) /* (a b -> a==b) */ \
X(neq) /* (a b -> a!=b) */ \
X(lt) /* (a b -> a<b) */ \
X(gt) /* (a b -> a>b) */ \
X(le) /* (a b -> a<=b) */ \
X(ge) /* (a b -> a>=b) */ \
X(if) /* (a -> ) if nonzero, skips the else branch, otherwise skips the if branch. does not nest */ \
X(else) /* optional */ \
X(end) /* ends the if conditional */ \
X(shr) /* (a b -> a >> b) */ \
X(shl) /* (a b -> a << b) */\
X(pop) /* (a b -> a) */\
X(peek) /* (a ... a_offset -> a) */ \
X(poke) /* (a ... a_offset b -> b ...)*/ \
X(x) /* this cell's x coordinate */ \
X(y) /* this cell's y coordinate */ \
X(wid) /* x dimension of the board */ \
X(hei) /* y dimension of the board */
typedef enum
{
RT_Custom,
WITH_BUILTIN(BUILTIN_ENUM)
RT_Unknown,
RT_MaximumPlus1
} routine_type;
static string_slice BuiltinIdentifiers[] =
{
WITH_BUILTIN(BUILTIN_IDENTIFIER)
};
typedef struct
{
routine_type Type;
string_slice Identifier;
string_slice Text;
} routine;
typedef struct
{
u32 Allocated;
u32 Count;
routine *Routines;
} routine_table;
typedef struct
{
parser_state State;
parser_state StateBeforeComment;
// debugging info
char C;
u32 Chari;
u32 LineCount;
i32 Number;
u32 CommentCount;
string_slice Identifier;
routine_table *RoutineTable;
routine CurrentRoutine;
b32 InsideFunctionName;
b32 InsideFunctionDefinition;
b32 NextCharacterIsLiteral;
u32 RecursionLevel;
// evaluation data
b32 Skip;
i32 *Stack;
u32 StackTop;
u32 X, Y;
} parser;
fprintf(stderr, \
"USAGE:" \
"%s [-f FILENAME]\n", \
Argv[0]); exit(ExitCode); \
} while(0);
fprintf(stderr, "Error at character '%c' (position %d, line %d): ", Parser->C, Parser->Chari, Parser->LineCount); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
exit(1); \
} while(0);
static routine
RoutineMatch(routine_table *Table, string_slice Identifier)
{
for(u32 Routinei = 0; Routinei < Table->Count; ++Routinei)
{
routine Rt = Table->Routines[Routinei];
if(Rt.Identifier.Count == Identifier.Count)
{
b32 Found = true;
for(u32 Chari = 0; Chari < Identifier.Count; ++Chari)
{
if(Identifier.Start[Chari] != Rt.Identifier.Start[Chari])
{
Found = false;
break;
}
}
if(Found)
{
return Rt;
}
}
}
return (routine){.Type = RT_Unknown};
}
static i32
StackPop(parser *Parser)
{
i32 Top = 0;
if(Parser->StackTop)
{
Top = Parser->Stack[--Parser->StackTop];
}
return Top;
}
// quietly fails if stack was going to overflow
static void
StackPush(parser *Parser, i32 Value)
{
if(Parser->StackTop < STACK_SIZE)
{
Parser->Stack[Parser->StackTop++] = Value;
}
}
static void ParserDoPass(parser *Parser, u32 InputSize, char *Input);
static void
Flush(parser *Parser, parser_state NewState)
{
Assert(Parser->CommentCount == 0);
switch(Parser->State)
{
case PARSING_ANY:
{ /* do nothing */ } break;
case PARSING_DECIMAL: // fallthrough
case PARSING_HEX:
{
if(!(Parser->InsideFunctionDefinition || Parser->InsideFunctionName || Parser->Skip))
{
StackPush(Parser, Parser->Number);
}
} break;
case PARSING_IDENTIFIER:
{
if(!(Parser->InsideFunctionDefinition || Parser->InsideFunctionName))
{
routine Routine = RoutineMatch(Parser->RoutineTable, Parser->Identifier);
if(Parser->Skip)
{
if(Routine.Type == RT_else || Routine.Type == RT_end)
{
Parser->Skip = false;
}
}
else
{
switch(Routine.Type)
{
case RT_Custom:
{
if(Parser->RecursionLevel < MAX_RECURSIVE_CALLS)
{
parser Inner =
{
.State = PARSING_ANY,
.StateBeforeComment = PARSING_ANY,
.C = 0,
.Chari = 0,
.LineCount = Parser->LineCount,
.Number = 0,
.CommentCount = 0,
.Identifier = {0},
.RoutineTable = Parser->RoutineTable,
.CurrentRoutine = {0},
.InsideFunctionDefinition = false,
.InsideFunctionName = false,
.NextCharacterIsLiteral = false,
.RecursionLevel = Parser->RecursionLevel + 1,
.Stack = Parser->Stack,
.StackTop = Parser->StackTop,
.X = Parser->X,
.Y = Parser->Y
};
ParserDoPass(&Inner, Routine.Text.Count, Routine.Text.Start);
Parser->StackTop = Inner.StackTop;
}
} break;
case Name: \
{ \
i32 B = StackPop(Parser); \
i32 A = StackPop(Parser); \
StackPush(Parser, Payload); \
} break;
BUILTIN_TWO(RT_add, A+B);
BUILTIN_TWO(RT_mul, A*B);
BUILTIN_TWO(RT_sub, A-B);
BUILTIN_TWO(RT_div, (B ? A/B : 0));
BUILTIN_TWO(RT_mod, (B ? A%B : 0));
BUILTIN_TWO(RT_or, A || B);
BUILTIN_TWO(RT_and, A && B);
BUILTIN_TWO(RT_band, A & B);
BUILTIN_TWO(RT_bor, A | B);
BUILTIN_TWO(RT_xor, A ^ B);
BUILTIN_TWO(RT_shr, A >> B);
BUILTIN_TWO(RT_shl, A << B);
BUILTIN_TWO(RT_eq, A == B);
BUILTIN_TWO(RT_neq, A != B);
BUILTIN_TWO(RT_gt, A > B);
BUILTIN_TWO(RT_lt, A < B);
BUILTIN_TWO(RT_ge, A >= B);
BUILTIN_TWO(RT_le, A <= B);
case RT_not:
{
i32 A = StackPop(Parser);
StackPush(Parser, !A);
} break;
case RT_bnot:
{
i32 A = StackPop(Parser);
StackPush(Parser, ~A);
} break;
case RT_swp:
{
i32 B = StackPop(Parser);
i32 A = StackPop(Parser);
StackPush(Parser, B);
StackPush(Parser, A);
} break;
case RT_if:
{
i32 Test = StackPop(Parser);
if(Test == 0) { Parser->Skip = true; }
} break;
case RT_else:
{
Parser->Skip = true;
} break;
case RT_end:
{
Parser->Skip = false;
} break;
case RT_x:
{
StackPush(Parser, (i32)Parser->X);
} break;
case RT_y:
{
StackPush(Parser, (i32)Parser->Y);
} break;
case RT_wid: // fallthrough
case RT_hei:
{
StackPush(Parser, (i32)BOARD_WIDTH);
} break;
case RT_pop:
{
StackPop(Parser);
} break;
case RT_peek:
{
i32 Offset = StackPop(Parser);
if(Parser->StackTop)
{
i32 TopOffset = (i32)(Parser->StackTop)-1;
if(Offset > TopOffset) { Offset = TopOffset; }
if(Offset < 0) { Offset = 0; }
StackPush(Parser, Parser->Stack[TopOffset-Offset]);
}
else
{
StackPush(Parser, 0);
}
} break;
case RT_poke:
{
i32 Value = StackPop(Parser);
i32 Offset = StackPop(Parser);
if(Parser->StackTop)
{
i32 TopOffset = (i32)(Parser->StackTop)-1;
if(Offset > TopOffset) { Offset = TopOffset; }
if(Offset < 0) { Offset = 0; }
Parser->Stack[TopOffset-Offset] = Value;
}
} break;
case RT_Unknown:
{
ParseError("unrecognized routine: %.*s\n", Parser->Identifier.Count, Parser->Identifier.Start);
} break;
InvalidDefaultCase;
}
}
}
else if(Parser->InsideFunctionName)
{
Parser->CurrentRoutine.Identifier = Parser->Identifier;
}
} break;
InvalidDefaultCase;
}
Parser->Number = 0;
Parser->Identifier = (string_slice){0};
Parser->State = NewState;
}
static void
AddRoutine(parser *Parser, routine_type Type, string_slice Identifier, string_slice Text)
{
routine_table *Table = Parser->RoutineTable;
routine Check = RoutineMatch(Table, Identifier);
if(Check.Type != RT_Unknown)
{
ParseError("a routine with the name '%.*s' already exists", Identifier.Count, Identifier.Start);
}
routine Routine =
{
.Type = Type,
.Identifier = Identifier,
.Text = Text
};
if(Type > RT_Custom)
{
Assert(Type < RT_MaximumPlus1);
Assert(Text.Count == 0);
Assert(Text.Start == 0);
}
else
{
Assert(Text.Start);
if(!Text.Count)
{
ParseError("an empty subroutine body for %.*s", Identifier.Count, Identifier.Start);
}
}
if(Table->Count + 1 > Table->Allocated)
{
Table->Allocated *= 2;
Table->Routines = realloc(Table->Routines, Table->Allocated*sizeof(routine));
}
Table->Routines[Table->Count++] = Routine;
}
static void
ParserDoPass(parser *Parser, u32 InputSize, char *Input)
{
for(u32 Chari = 0; Chari < InputSize; ++Chari)
{
char C = Input[Chari];
Parser->C = C;
Parser->Chari = Chari;
if(Parser->NextCharacterIsLiteral)
{
Parser->NextCharacterIsLiteral = false;
Parser->Number = C;
Flush(Parser, PARSING_ANY);
}
else if(Parser->State == PARSING_COMMENT)
{
if(C == ')')
{
--Parser->CommentCount;
if(Parser->CommentCount == 0)
{
Parser->State = Parser->StateBeforeComment;
}
}
else if(C == '(')
{
++Parser->CommentCount;
}
}
else if(Parser->InsideFunctionName)
{
if(C == '{')
{
if(Chari + 1 == InputSize)
{
ParseError("empty function body");
}
Flush(Parser, PARSING_ANY);
Parser->InsideFunctionName = 0;
Parser->InsideFunctionDefinition = 1;
Assert(Parser->CurrentRoutine.Identifier.Count);
Assert(Parser->CurrentRoutine.Identifier.Start);
Parser->CurrentRoutine.Text.Start = Input + Chari + 1;
Assert(Parser->CurrentRoutine.Text.Count == 0);
}
else if((C >= 'a' && C <= 'z') || (C == '_'))
{
if(Parser->State != PARSING_IDENTIFIER)
{
Flush(Parser, PARSING_IDENTIFIER);
}
if(!Parser->Identifier.Start)
{
Parser->Identifier.Start = Input + Chari;
}
++Parser->Identifier.Count;
}
else if(C == '\n' || C == '\r' || C == '\n' || C == '\t' || C == ' ')
{
if(C == '\n') { ++Parser->LineCount; }
Flush(Parser, PARSING_ANY);
}
else
{
ParseError("a routine body must follow the routine definition");
}
}
else
{
switch(C)
{
case '0': // fallthrough
case '1': // fallthrough
case '2': // fallthrough
case '3': // fallthrough
case '4': // fallthrough
case '5': // fallthrough
case '6': // fallthrough
case '7': // fallthrough
case '8': // fallthrough
case '9':
{
if(Parser->State == PARSING_HEX)
{
Parser->Number *= 16;
Parser->Number += C - '0';
}
else
{
if(Parser->State != PARSING_DECIMAL)
{
Flush(Parser, PARSING_DECIMAL);
}
Parser->Number *= 10;
Parser->Number += C - '0';
}
} break;
case '#':
{
Flush(Parser, PARSING_HEX);
} break;
case '$':
{
Flush(Parser, PARSING_IDENTIFIER);
Parser->InsideFunctionName = 1;
} break;
case '(':
{
if(Parser->CommentCount == 0)
{
Parser->StateBeforeComment = Parser->State;
Flush(Parser, PARSING_COMMENT);
}
++Parser->CommentCount;
} break;
case ')':
{
ParseError("unexpected comment closing brace");
} break;
case '{':
{
ParseError("unexpected opening brace");
} break;
case '}':
{
if(!Parser->InsideFunctionDefinition)
{
ParseError("unexpected closing brace");
}
Parser->CurrentRoutine.Text.Count = (u32)((Input + Chari) - Parser->CurrentRoutine.Text.Start);
if((Parser->X == 0) && (Parser->Y == BOARD_WIDTH-1))
{
AddRoutine(Parser, RT_Custom, Parser->CurrentRoutine.Identifier, Parser->CurrentRoutine.Text);
}
Flush(Parser, PARSING_ANY);
Parser->CurrentRoutine = (routine){0};
Parser->InsideFunctionDefinition = 0;
} break;
case 'A': // fallthrough
case 'B': // fallthrough
case 'C': // fallthrough
case 'D': // fallthrough
case 'E': // fallthrough
case 'F':
{
if(Parser->State != PARSING_HEX)
{
ParseError("A-F can only be used in a hex number");
}
Parser->Number *= 16;
Parser->Number += C - 'A' + 10;
} break;
case 'a': // fallthrough
case 'b': // fallthrough
case 'c': // fallthrough
case 'd': // fallthrough
case 'e': // fallthrough
case 'f': // fallthrough
case 'g': // fallthrough
case 'h': // fallthrough
case 'i': // fallthrough
case 'j': // fallthrough
case 'k': // fallthrough
case 'l': // fallthrough
case 'm': // fallthrough
case 'n': // fallthrough
case 'o': // fallthrough
case 'p': // fallthrough
case 'q': // fallthrough
case 'r': // fallthrough
case 's': // fallthrough
case 't': // fallthrough
case 'u': // fallthrough
case 'v': // fallthrough
case 'w': // fallthrough
case 'x': // fallthrough
case 'y': // fallthrough
case 'z': // fallthrough
case '_':
{
if(Parser->State != PARSING_IDENTIFIER)
{
Flush(Parser, PARSING_IDENTIFIER);
}
if(!Parser->Identifier.Start)
{
Parser->Identifier.Start = Input + Chari;
}
++Parser->Identifier.Count;
} break;
case '\\':
{
Flush(Parser, PARSING_DECIMAL);
Parser->NextCharacterIsLiteral = 1;
} break;
case '\n':
++Parser->LineCount; // fallthrough
case '\r': // fallthrough
case '\t': // fallthrough
case ' ':
{
Flush(Parser, PARSING_ANY);
} break;
default:
{
ParseError("unexpected character");
} break;
}
}
}
Flush(Parser,PARSING_ANY);
}
int main(int Argc, char **Argv)
{
FILE *InputFile = stdin;
for(int Argi = 1; Argi < Argc; ++Argi)
{
char *Arg = Argv[Argi];
if(Arg[0] == '-' && Arg[1] == 'f' && Arg[2] == 0)
{
if(Argi + 1 < Argc)
{
InputFile = fopen(Argv[Argi+1], "rb");
if(!InputFile)
{
UsageAndExit(1);
}
++Argi;
}
else
{
UsageAndExit(1);
}
}
}
char *Input = malloc(MAX_PROGRAM_SIZE);
u32 InputSize = fread(Input, 1, MAX_PROGRAM_SIZE, InputFile);
if(InputSize == 0 || InputSize == MAX_PROGRAM_SIZE)
{
UsageAndExit(1);
}
u32 RoutinesToAllocate = (u32)RT_MaximumPlus1 + 32;
routine_table RoutineTable =
{
.Allocated = RoutinesToAllocate,
.Count = 0,
.Routines = (routine *)malloc(sizeof(routine)*RoutinesToAllocate)
};
parser Parser =
{
.State = PARSING_ANY,
.StateBeforeComment = PARSING_ANY,
.Number = 0,
.CommentCount = 0,
.Identifier = {.Start = 0, .Count = 0},
.RoutineTable = &RoutineTable,
.CurrentRoutine = {0},
.InsideFunctionDefinition = false,
.NextCharacterIsLiteral = false,
};
// create the builtins
string_slice EmptyText = {.Count = 0, .Start = 0};
for(u32 Builtini = (u32)RT_Custom + 1;
Builtini < RT_MaximumPlus1;
++Builtini)
{
AddRoutine(&Parser, (routine_type)Builtini, BuiltinIdentifiers[Builtini], EmptyText);
}
i32 *Stack = calloc(1, sizeof(i32)*STACK_SIZE);
for(u32 Y = BOARD_WIDTH-1; ; --Y)
{
for(u32 X = 0; X < BOARD_WIDTH; ++X)
{
Parser = (parser)
{
.State = PARSING_ANY,
.StateBeforeComment = PARSING_ANY,
.Number = 0,
.CommentCount = 0,
.Identifier = {0},
.CurrentRoutine = {0},
.InsideFunctionName = false,
.InsideFunctionDefinition = false,
.Stack = Stack,
.StackTop = 0,
.RoutineTable = &RoutineTable,
.RecursionLevel = 0,
.Skip = false,
.X = X,
.Y = Y
};
ParserDoPass(&Parser, InputSize, Input);
i32 Result = Parser.StackTop ? Parser.Stack[Parser.StackTop-1] : 0;
if(Result >= ' ' && Result <= '~')
{
printf("%c", Result);
}
else
{
printf("[%d]", Result);
}
}
printf("\n");
if(Y == 0) { break; }
}
}
text/x-c
This content has been proxied by September (ba2dc).