Skip to content

Commit

Permalink
refactor: preprocessor statements
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyJasonBennett committed Nov 19, 2023
1 parent 734686c commit 3a3c581
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
7 changes: 7 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export class BlockStatement extends Node {
}
}

export class PreprocessorStatement extends Node {
constructor(public name: string, public value: Node[] | null) {
super()
}
}

export class Type extends Node {
constructor(public name: string, public parameters: (Type | Literal | Identifier)[] | null) {
super()
Expand Down Expand Up @@ -173,3 +179,4 @@ export type AST =
| ContinueStatement
| BreakStatement
| DiscardStatement
| PreprocessorStatement
14 changes: 14 additions & 0 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ContinueStatement,
BreakStatement,
DiscardStatement,
PreprocessorStatement,
} from './ast'

const EOL_REGEX = /\n$/
Expand Down Expand Up @@ -133,6 +134,19 @@ function format(node: AST | null): string {
line = 'break;\n'
} else if (node instanceof DiscardStatement) {
line = 'discard;\n'
} else if (node instanceof PreprocessorStatement) {
let value = ''
if (node.value) {
if (node.name === 'include') {
value = ` <${format(node.value[0])}>`
} else if (node.name === 'extension') {
value = ` ${node.value.map(format).join(': ')}`
} else {
value = ` ${node.value.map(format).join(' ')}`
}
}

line = `#${node.name}${value}\n`
}

return line
Expand Down
53 changes: 51 additions & 2 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
StructDeclaration,
PrecisionStatement,
ArrayExpression,
PreprocessorStatement,
} from './ast'
import { generate } from './generator'
import { type Token, tokenize } from './tokenizer'
Expand Down Expand Up @@ -455,6 +456,34 @@ function parsePrecision(): PrecisionStatement {
return new PrecisionStatement(precision as any, type)
}

function parsePreprocessor(): PreprocessorStatement {
const name = tokens[i++].value

const body = consumeUntil('\\').slice(0, -1)
let value: AST[] | null = null

if (name !== 'else' && name !== 'endif') {
value = []

if (name === 'define') {
const left = parseExpression([body.shift()!])!
const right = parseExpression(body)!
value.push(left, right)
} else if (name === 'extension') {
const left = parseExpression([body.shift()!])!
body.shift() // skip :
const right = parseExpression(body)!
value.push(left, right)
} else if (name === 'include') {
value.push(parseExpression(body.slice(1, -1))!)
} else {
value.push(parseExpression(body)!)
}
}

return new PreprocessorStatement(name, value)
}

function parseStatements(): AST[] {
const body: AST[] = []
let scopeIndex = 0
Expand All @@ -467,7 +496,9 @@ function parseStatements(): AST[] {

let statement: AST | null = null

if (token.type === 'keyword') {
if (token.value === '#') {
statement = parsePreprocessor()
} else if (token.type === 'keyword') {
if (token.value === 'case' || token.value === 'default') {
i--
break
Expand Down Expand Up @@ -504,12 +535,19 @@ function parseBlock(): BlockStatement {
return new BlockStatement(body)
}

const DIRECTIVE_REGEX = /(^\s*#[^\\]*?)(\n|\/[\/\*])/gm

/**
* Parses a string of GLSL or WGSL code into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
*/
export function parse(code: string): AST[] {
// TODO: preserve
// Remove (implicit) version header
code = code.replace('#version 300 es', '')

// Escape newlines after directives, skip comments
code = code.replace(DIRECTIVE_REGEX, '$1\\$2')

// TODO: preserve
tokens = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
i = 0

Expand Down Expand Up @@ -553,6 +591,17 @@ void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
#line 0
#define TEST 1
#extension all: disable
#error test
#pragma optimize(on)
#include <chunk>
#ifdef TEST
test;
#endif
method(true);
foo.bar();
Expand Down
60 changes: 60 additions & 0 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SwitchCase,
PrecisionStatement,
ArrayExpression,
PreprocessorStatement,
} from 'shaderkit'

describe('parser', () => {
Expand Down Expand Up @@ -371,4 +372,63 @@ describe('parser', () => {
expect(statement.members[2]).toBeInstanceOf(Literal)
expect((statement.members[2] as Literal).value).toBe('1.5')
})

it('parses preprocessor statements', () => {
{
const statement = parse('#define TEST 1\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('define')
console.log(statement.value)
expect(statement.value!.length).toBe(2)
expect(statement.value![0]).toBeInstanceOf(Identifier)
expect((statement.value![0] as Identifier).value).toBe('TEST')
expect(statement.value![1]).toBeInstanceOf(Literal)
expect((statement.value![1] as Literal).value).toBe('1')
}

{
const statement = parse('#extension all: disable\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('extension')
expect(statement.value!.length).toBe(2)
}

{
const statement = parse('#pragma optimize(on)\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('pragma')
expect(statement.value!.length).toBe(1)
expect(statement.value![0]).toBeInstanceOf(CallExpression)
expect((statement.value![0] as CallExpression).callee).toBeInstanceOf(Identifier)
expect(((statement.value![0] as CallExpression).callee as Identifier).value).toBe('optimize')
expect((statement.value![0] as CallExpression).args.length).toBe(1)
expect((statement.value![0] as CallExpression).args[0]).toBeInstanceOf(Identifier)
expect(((statement.value![0] as CallExpression).args[0] as Identifier).value).toBe('on')
}

{
const statement = parse('#include <chunk>\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('include')
expect(statement.value!.length).toBe(1)
expect(statement.value![0]).toBeInstanceOf(Identifier)
expect((statement.value![0] as Identifier).value).toBe('chunk')
}

{
const statement = parse('#ifdef TEST\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('ifdef')
expect(statement.value!.length).toBe(1)
expect(statement.value![0]).toBeInstanceOf(Identifier)
expect((statement.value![0] as Identifier).value).toBe('TEST')
}

{
const statement = parse('#endif\n')[0] as PreprocessorStatement
expect(statement).toBeInstanceOf(PreprocessorStatement)
expect(statement.name).toBe('endif')
expect(statement.value).toBe(null)
}
})
})

0 comments on commit 3a3c581

Please sign in to comment.