Skip to content

Commit

Permalink
Implement modules
Browse files Browse the repository at this point in the history
  • Loading branch information
frostburn committed May 6, 2024
1 parent 07eec13 commit a7b38b9
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 3 deletions.
44 changes: 44 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,44 @@ export type DeferStatement = {
body: Statement;
};

export type ModuleDeclaration = {
type: 'ModuleDeclaration';
name: string;
body: Statement[];
};

export type ExportConstantStatement = {
type: 'ExportConstantStatement';
parameter: Parameter;
};

export interface ExportFunctionStatement
extends Omit<FunctionDeclaration, 'type'> {
type: 'ExportFunctionStatement';
}

export type ExportAllStatement = {
type: 'ExportAllStatement';
module: string;
};

export type ImportElement = {
type: 'ImportElement';
id: string;
alias: null | string;
};

export type ImportStatement = {
type: 'ImportStatement';
elements: ImportElement[];
module: string;
};

export type ImportAllStatement = {
type: 'ImportAllStatement';
module: string;
};

export type ExpressionStatement = {
type: 'ExpressionStatement';
expression: Expression;
Expand All @@ -228,6 +266,12 @@ export type Statement =
| IterationStatement
| TryStatement
| DeferStatement
| ModuleDeclaration
| ExportConstantStatement
| ExportFunctionStatement
| ExportAllStatement
| ImportStatement
| ImportAllStatement
| ThrowStatement
| BreakStatement
| ContinueStatement
Expand Down
75 changes: 75 additions & 0 deletions src/grammars/sonic-weave.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
const RESERVED_WORDS = new Set([
'al',
'and',
'as',
'break',
'by',
'catch',
Expand All @@ -20,11 +21,14 @@
'drop',
'ed',
'else',
'export',
'false',
'finally',
'fn',
'for',
'from',
'if',
'import',
'in',
'lest',
'let',
Expand All @@ -34,6 +38,7 @@
'min',
'mod',
'modc',
'module',
'niente',
'not',
'of',
Expand Down Expand Up @@ -130,6 +135,7 @@ Program

AlToken = @'al' !IdentifierPart
AndToken = @'and' !IdentifierPart
AsToken = @'as' !IdentifierPart
BreakToken = @'break' !IdentifierPart
ByToken = @'by' !IdentifierPart
CatchToken = @'catch' !IdentifierPart
Expand All @@ -140,10 +146,13 @@ DotToken = @'dot' !IdentifierPart
DropToken = @'drop' !IdentifierPart
EdToken = @'ed' !IdentifierPart
ElseToken = @'else' !IdentifierPart
ExportToken = @'export' !IdentifierPart
FalseToken = @'false' !IdentifierPart
FinallyToken = @'finally' !IdentifierPart
ForToken = @'for' !IdentifierPart
FromToken = @'from' !IdentifierPart
IfToken = @'if' !IdentifierPart
ImportToken = @'import' !IdentifierPart
InToken = @'in' !IdentifierPart
LestToken = @'lest' !IdentifierPart
LetToken = @'let' !IdentifierPart
Expand All @@ -153,6 +162,7 @@ MatrixDotToken = @'mdot' !IdentifierPart
MinToken = @'min' !IdentifierPart
ModToken = @'mod' !IdentifierPart
ModCeilingToken = @'modc' !IdentifierPart
ModuleToken = @'module' !IdentifierPart
NoneToken = @'niente' !IdentifierPart
NotToken = @'not' !IdentifierPart
OfToken = @'of' !IdentifierPart
Expand Down Expand Up @@ -199,6 +209,12 @@ Statement
/ IterationStatement
/ TryStatement
/ DeferStatement
/ ModuleDeclaration
/ ExportConstantStatement
/ ExportFunctionStatement
/ ExportAllStatement
/ ImportAllStatement
/ ImportStatement
/ EmptyStatement

ReassignmentTail
Expand Down Expand Up @@ -548,6 +564,65 @@ DeferStatement
};
}

ModuleDeclaration
= ModuleToken _ name: Identifier _ body: BlockStatement {
return {
type: 'ModuleDeclaration',
name: name.id,
body: body.body,
}
}

ExportConstantStatement
= ExportToken _ ConstToken _ parameter: ParameterWithDefault EOS {
return {
type: 'ExportConstantStatement',
parameter,
};
}

ExportFunctionStatement
= ExportToken _ riff: FunctionDeclaration {
return {
...riff,
type: 'ExportFunctionStatement',
};
}

ExportAllStatement
= ExportToken _ '*' _ FromToken _ name: Identifier EOS {
return {
type: 'ExportAllStatement',
module: name.id,
};
}

ImportElement
= name: Identifier alias: (_ AsToken _ @Identifier)? {
return {
type: 'ImportElement',
id: name.id,
alias: alias && alias.id,
};
}

ImportStatement
= ImportToken _ elements: ImportElement|1.., _ ',' _| _ FromToken _ name: Identifier EOS {
return {
type: 'ImportStatement',
elements,
module: name.id,
};
}

ImportAllStatement
= ImportToken _ '*' _ FromToken _ name: Identifier EOS {
return {
type: 'ImportAllStatement',
module: name.id,
};
}

EmptyStatement
= (_ ';' / __ SingleLineComment LineTerminatorSequence) {
return {
Expand Down
50 changes: 50 additions & 0 deletions src/parser/__tests__/modules.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {describe, it, expect} from 'vitest';
import {evaluateSource} from '../../parser';
import {Interval} from '../../interval';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function parseSource(source: string) {
const visitor = evaluateSource(source, false);
return visitor.mutables.get('$') as Interval[];
}

function expand(source: string) {
const visitor = evaluateSource(source, false);
return visitor.expand(visitor.rootContext!).split('\n');
}

describe('SonicWeave module system', () => {
it('declares modules and imports from them', () => {
const scale = expand(`
module foo {
"This is a module docstring.";
export const bar = 1;
export riff baz(qux) {
return qux + bar;
}
3/2 "module scale should be ignored";
}
{
import * from foo;
bar; // 1
module corge {
2/1 "block-scope modules make little sense, but banning them is more work."
export * from foo;
}
import baz as grault from corge;
grault(10); // 11
}
import bar, baz as quux from foo;
quux(100); // 101
bar + 1000; // 1001
`);
expect(scale).toEqual(['1', '11', '101', '1001']);
});
});
21 changes: 21 additions & 0 deletions src/parser/__tests__/sonic-weave-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,27 @@ describe('SonicWeave Abstract Syntax Tree parser', () => {
],
});
});

it('has module syntax', () => {
const ast = parse(`
module foo {
export const bar = 1;
export riff baz(qux) {
return qux + bar;
}
}
import bar, baz as quux from foo;
`);
expect(ast.body[0].type).toBe('ModuleDeclaration');
expect(ast.body[1]).toEqual({
type: 'ImportStatement',
elements: [
{type: 'ImportElement', id: 'bar', alias: null},
{type: 'ImportElement', id: 'baz', alias: 'quux'},
],
module: 'foo',
});
});
});

describe('Automatic semicolon insertion', () => {
Expand Down
Loading

0 comments on commit a7b38b9

Please sign in to comment.