Skip to content

Commit

Permalink
Add simple if condition support
Browse files Browse the repository at this point in the history
  • Loading branch information
Veikko Soininen committed Oct 22, 2023
1 parent 8eb1f4e commit c2eb1a6
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 27 deletions.
42 changes: 19 additions & 23 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,31 +64,27 @@ jobs:
- name: Build
run: npm run build

- name: Compile
- name: Compile, Assemble, Link, and Run
run: |
npm run start tests/inputs/simple.c
npm run start tests/inputs/fn.c
INPUTS=("simple" "fn" "if")
- name: Assemble and Link
run: |
nasm -f elf32 simple.asm -o simple.o
gcc -m32 -o simple simple.o
nasm -f elf32 fn.asm -o fn.o
gcc -m32 -o fn fn.o
for FILE in "${INPUTS[@]}"; do
npm run start "tests/inputs/$FILE.c"
done
for FILE in "${INPUTS[@]}"; do
nasm -f elf32 "$FILE.asm" -o "$FILE.o"
gcc -m32 -o "$FILE" "$FILE.o"
done
- name: Run the executables
run: |
set +e
./simple
RETURN_VALUE=$?
if [[ $RETURN_VALUE -ne 5 ]]; then
echo "Unexpected return value from the executable: $RETURN_VALUE"
exit 1
fi
./fn
RETURN_VALUE=$?
if [[ $RETURN_VALUE -ne 5 ]]; then
echo "Unexpected return value from the executable: $RETURN_VALUE"
exit 2
fi
for FILE in "${INPUTS[@]}"; do
./"$FILE"
RETURN_VALUE=$?
if [[ $RETURN_VALUE -ne 5 ]]; then
echo "Unexpected return value from the $FILE executable: $RETURN_VALUE"
exit 1
fi
done
exit 0
9 changes: 8 additions & 1 deletion src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export type ASTNode =
| ReturnStatementNode
| FunctionDeclarationNode
| ParameterNode
| FunctionCallNode;
| FunctionCallNode
| IfStatementNode;

export interface IdentifierNode {
type: 'Identifier';
Expand Down Expand Up @@ -60,3 +61,9 @@ export interface FunctionCallNode {
identifier: IdentifierNode;
arguments: ASTNode[];
}

export interface IfStatementNode {
type: 'IfStatement';
condition: ASTNode;
body: ASTNode[];
}
24 changes: 24 additions & 0 deletions src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {

let assemblyCode = '';
let dataSegment = 'section .data\n';
let labelCounter = 0;

function generateAssemblyCode(ast: ASTNode[]): string {
assemblyCode = '';
Expand Down Expand Up @@ -69,6 +70,15 @@ function generateStatement(node: ASTNode, parameters: ParameterNode[]): void {
generateExpression(node.argument, parameters);
break;

case 'IfStatement': {
const endLabel = generateUniqueLabel('end_if');
generateCondition(node.condition, endLabel, parameters);
node.body.forEach((statement) =>
generateStatement(statement, parameters),
);
assemblyCode += `${endLabel}:\n`;
break;
}
default:
throw new Error(`Unsupported statement type: ${node.type}`);
}
Expand Down Expand Up @@ -124,6 +134,20 @@ function isParameter(
);
}

function generateCondition(
node: ASTNode,
endLabel: string,
parameters: ParameterNode[],
): void {
generateExpression(node, parameters);
assemblyCode += 'test eax, eax\n';
assemblyCode += `je ${endLabel}\n`;
}

function generateUniqueLabel(base: string): string {
return `${base}_${labelCounter++}`;
}

function getParameterOffset(
parameters: ParameterNode[],
paramName: IdentifierNode,
Expand Down
17 changes: 17 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
BinaryExpressionNode,
ParameterNode,
FunctionCallNode,
IfStatementNode,
} from './ast';

let tokens: Token[] = [];
Expand Down Expand Up @@ -81,6 +82,8 @@ function parseStatement(): ASTNode {
return parseReturnStatement();
} else if (tokens[cursor].type === TokenType.Identifier) {
return parseFunctionCallOrAssignment();
} else if (tokens[cursor].type === TokenType.IfKeyword) {
return parseIfStatement();
} else {
throw new Error(`Unexpected token: ${tokens[cursor].type}`);
}
Expand Down Expand Up @@ -114,6 +117,20 @@ function parseFunctionCallOrAssignment(): ASTNode {
}
}

function parseIfStatement(): IfStatementNode {
match(TokenType.IfKeyword);
match(TokenType.LeftParen);
const condition = parseExpression();
match(TokenType.RightParen);
const body = parseBlock();

return {
type: 'IfStatement',
condition,
body,
};
}

function parseArguments(): ASTNode[] {
const args: ASTNode[] = [];
match(TokenType.LeftParen);
Expand Down
5 changes: 5 additions & 0 deletions src/semantic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export function performSemanticAnalysis(asts: ASTNode[]): void {
node.arguments.forEach(traverse);
break;

case 'IfStatement':
traverse(node.condition);
node.body.forEach(traverse);
break;

case 'Identifier':
case 'Literal':
// No semantic analysis needed for identifiers and literals
Expand Down
7 changes: 4 additions & 3 deletions src/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum TokenType {
LeftParen = 'LEFT_PAREN',
RightParen = 'RIGHT_PAREN',
Comma = 'COMMA',
IfKeyword = 'IF_KEYWORD',
}

export interface Token {
Expand All @@ -22,8 +23,6 @@ export interface Token {
}

export function tokenize(code: string): Token[] {
const keywords = new Set(['int']);

const tokens: Token[] = [];
let cursor = 0;

Expand Down Expand Up @@ -84,13 +83,15 @@ export function tokenize(code: string): Token[] {
identifier += code[cursor];
}

if (keywords.has(identifier)) {
if (identifier === 'int') {
tokens.push({ type: TokenType.IntKeyword, value: identifier });
} else if (identifier === 'return') {
tokens.push({
type: TokenType.ReturnKeyword,
value: identifier,
});
} else if (identifier === 'if') {
tokens.push({ type: TokenType.IfKeyword, value: identifier });
} else {
tokens.push({ type: TokenType.Identifier, value: identifier });
}
Expand Down
10 changes: 10 additions & 0 deletions tests/inputs/if.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
int main() {
int c = 0;
if (c) {
c = c + 9;
}
if (1) {
c = c + 5;
}
return c;
}
35 changes: 35 additions & 0 deletions tests/silly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,41 @@ add esp, 4
mov [c], eax
mov eax, [c]
ret
`;

const generatedCode = compile(code);
expect(generatedCode).toEqual(expectedAssemblyCode);
});

test('should compile if.c', () => {
const code = readFileSync(`${testFilePath}/if.c`, 'utf8');
const expectedAssemblyCode = `section .data
c dd 0
section .text
global main
main:
mov eax, [c]
test eax, eax
je end_if_0
mov eax, 9
push eax
mov eax, [c]
pop ebx
add eax, ebx
mov [c], eax
end_if_0:
mov eax, 1
test eax, eax
je end_if_1
mov eax, 5
push eax
mov eax, [c]
pop ebx
add eax, ebx
mov [c], eax
end_if_1:
mov eax, [c]
ret
`;

const generatedCode = compile(code);
Expand Down

0 comments on commit c2eb1a6

Please sign in to comment.