Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"test:jest": "jest --config jest.config.js",
"test": "npm run test:formatting && npm run test:typecheck && npm run test:lint && npm run test:build && npm run test:jest",
"cli": "npm run build && node --enable-source-maps dist/cli.js",
"cli-debug": "npm run build && node --inspect-brk dist/cli.js",
"test:build": "npm run build && node \"dist/markdown-tests/build.js\"",
"cover": "npm run build && node \"dist/cover/index.js\"",
"cover-all": "npm run build && node \"dist/cover/index.js\" -a"
Expand Down
37 changes: 21 additions & 16 deletions src/IR/IR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ import {
type VarDeclarationBlock,
} from "./assignments";
import { type Array, type List, type Table, type Set } from "./collections";
import {
type Op,
type Infix,
type ConditionalOp,
type FunctionCall,
type MethodCall,
type Prefix,
type IndexCall,
type KeyValue,
type RangeIndexCall,
type Function,
type NamedArg,
type ImplicitConversion,
type PropertyCall,
type Postfix,
import type {
Op,
Infix,
ConditionalOp,
FunctionCall,
MethodCall,
Prefix,
IndexCall,
KeyValue,
RangeIndexCall,
Function,
NamedArg,
ImplicitConversion,
PropertyCall,
Postfix,
ScanningMacroCall,
} from "./exprs";
import type { FunctionDefinition } from "./functions";
import {
type ForRange,
type ForEach,
Expand All @@ -41,7 +43,7 @@ import {
type Integer,
type Text,
} from "./terminals";
import { type Block, type If, type Import, type Variants } from "./toplevel";
import type { Block, If, Import, Variants } from "./toplevel";
import { type Type } from "./types";

export * from "./assignments";
Expand All @@ -52,6 +54,7 @@ export * from "./loops";
export * from "./terminals";
export * from "./toplevel";
export * from "./types";
export * from "./functions";

export interface BaseNode {
readonly source?: SourcePointer;
Expand Down Expand Up @@ -90,6 +93,7 @@ export type Node =
| ForArgv
| If
// Other nodes
| FunctionDefinition
| ImplicitConversion
| VarDeclaration
| VarDeclarationWithAssignment
Expand All @@ -99,6 +103,7 @@ export type Node =
| MutatingInfix
| IndexCall
| RangeIndexCall
| ScanningMacroCall
Comment thread
jared-hughes marked this conversation as resolved.
| MethodCall
| PropertyCall
| Infix
Expand Down
27 changes: 27 additions & 0 deletions src/IR/exprs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
isBinary,
booleanNotOpCode,
type Text,
type IDCastable,
castID,
type VariadicOpCode,
isCommutative,
isOpCode,
Expand Down Expand Up @@ -71,6 +73,20 @@ export interface FunctionCall extends BaseNode {
readonly args: readonly Node[];
}

/**
* ScanningMacroCall is currently necessary to represent (TeX)
* \newcount\x \x123
* since a regular FunctionCall would require curly braces since 123 is three tokens.
* \def\f#1{(#1)} \f{123}
* This is a correctness issue (not just golfing) since curly braces does not work for counter:
* \newcount\x \x{123} % Missing number, treated as zero.
*/
export interface ScanningMacroCall extends BaseNode {
readonly kind: "ScanningMacroCall";
readonly func: Node;
readonly args: readonly Node[];
}

export interface MethodCall extends BaseNode {
readonly kind: "MethodCall";
readonly object: Node;
Expand Down Expand Up @@ -399,6 +415,17 @@ export function functionCall(
};
}

export function scanningMacroCall(
func: IDCastable,
...args: readonly Node[]
): ScanningMacroCall {
return {
kind: "ScanningMacroCall",
func: castID(func),
args,
};
}

export function methodCall(
object: Node,
ident: string | Identifier,
Expand Down
65 changes: 65 additions & 0 deletions src/IR/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Spine } from "../common/Spine";
import {
type Identifier,
type BaseNode,
type Node,
type IDCastable,
castID,
} from "./IR";

// This file is for functions and macros.

/**
* FunctionDefinition is currently only used for TeX, where the identifiers are
* emitted as macro symbols:
*
* \def\f#1#2{(#1,#2,#1,#2)}
*
* While TeX calls these "macros", they happen at runtime.
* Macros (like #define from C) are compile-time.
*
* Note this currently assumes the macros don't do any scanning.
* E.g. TeX supports
* \def\f#1,#2;{#1#2#1#2} \f123,456;
* in lieu of
* \def\f#1#2{#1#2#1#2} \f{123}{456}
* For now, we don't allow the former. Allowing it will need a different node type.
*/
export interface FunctionDefinition extends BaseNode {
readonly kind: "FunctionDefinition";
readonly name: Identifier;
readonly args: readonly Identifier[];
readonly body: Node;
/** Does the definition apply to all parent scopes too?
* In TeX: false = \def or \edef; true = \gdef or \xdef */
readonly isGlobal: boolean;
/** Does the definition expand its argument before defining?
* In TeX: false = \def or \gdef; true = \edef or \xdef */
readonly isExpanded: boolean;
}

export function functionDefinition(
name: IDCastable,
args: readonly IDCastable[],
body: Node,
opts: { isGlobal?: boolean; isExpanded?: boolean } = {},
): FunctionDefinition {
const isExpanded = opts.isExpanded ?? false;
if (isExpanded && args.length > 0) {
throw new Error("Expanded definition cannot have any args.");
}
return {
kind: "FunctionDefinition",
name: castID(name),
args: args.map(castID),
body,
isGlobal: opts.isGlobal ?? false,
isExpanded,
};
}

export function functionDefinitionNestingDepth(s: Spine, d: number = 0) {
if (s.node.kind === "FunctionDefinition") ++d;
if (s.parent === null) return d;
return functionDefinitionNestingDepth(s.parent, d);
}
7 changes: 7 additions & 0 deletions src/IR/terminals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export interface Text<Value extends string = string> extends BaseNode {
readonly value: Value;
}

export type IDCastable = string | Identifier;
Comment thread
MichalMarsalek marked this conversation as resolved.

export function castID(name: IDCastable) {
if (typeof name === "string") return id(name);
return name;
}

let unique = 0;
export function id(name?: string, builtin: boolean = false): Identifier {
return { kind: "Identifier", name: name ?? `unique#${unique++}`, builtin };
Expand Down
5 changes: 4 additions & 1 deletion src/IR/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ export const int53Type: Type = integerType(
-9007199254740992n,
9007199254740991n,
);
export const int32Type: Type = integerType(-2147483648n, 2147483647n);

export function type(
type: Type | "void" | "boolean" | "int64" | "int53",
type: Type | "void" | "boolean" | "int64" | "int53" | "int32",
): Type {
switch (type) {
case "void":
Expand All @@ -99,6 +100,8 @@ export function type(
return int64Type;
case "int53":
return int53Type;
case "int32":
return int32Type;
default:
return type;
}
Expand Down
13 changes: 11 additions & 2 deletions src/common/Spine.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { type IR, isOp, op, isOfKind, block, type Node } from "../IR";
import { type IR, isOp, op, block, isOfKind, type Node } from "../IR";
import type { VisitorContext, CompilationContext } from "./compile";
import { getChild, getChildFragments, type PathFragment } from "./fragments";
import {
getChild,
getChildFragments,
getPathProp,
type PathFragment,
} from "./fragments";
import { replaceAtIndex } from "./arrays";

/** A Spine points to one node and keeps track of all of its ancestors up to
Expand All @@ -24,6 +29,10 @@ export class Spine<N extends IR.Node = IR.Node> {
return this.parent === null;
}

getPathProp() {
return this.pathFragment !== null ? getPathProp(this.pathFragment) : null;
}

/** Get a list of all child spines. */
getChildSpines(): Spine[] {
return Array.from(getChildFragments(this.node)).map((n) =>
Expand Down
5 changes: 2 additions & 3 deletions src/common/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ export function containsMultiNode(exprs: readonly IR.Node[]): boolean {
export class EmitError extends PolygolfError {
expr: Node;
constructor(expr: Node, detail?: string) {
if (detail === undefined && "op" in expr && expr.op !== null)
detail = expr.op;
const kind = expr.kind + ("op" in expr ? `[${expr.op}]` : "");
detail = detail === undefined ? "" : ` (${detail})`;
const message = `emit error - ${expr.kind}${detail} not supported.`;
const message = `emit error - ${kind}${detail} not supported.`;
super(message, expr.source);
this.name = "EmitError";
this.expr = expr;
Expand Down
5 changes: 5 additions & 0 deletions src/common/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export type PathFragment =
readonly index: number;
};

export function getPathProp(frag: PathFragment) {
if (typeof frag === "string") return frag;
else return frag.prop;
}

export function getChild(node: IR.Node, pathFragment: PathFragment): IR.Node {
if (typeof pathFragment === "string") {
return (node as any)[pathFragment];
Expand Down
2 changes: 2 additions & 0 deletions src/common/getType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export function calcTypeAndResolveOpCode(
switch (expr.kind) {
case "Function":
return functionType(expr.args.map(type), type(expr.expr));
case "FunctionDefinition":
return functionType(expr.args.map(type), type(expr.body));
case "Block":
case "VarDeclaration":
return voidType;
Expand Down
30 changes: 26 additions & 4 deletions src/common/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ function introducedSymbols(
.filter(isIdent())
.filter((x) => !existing.has(x.name))
.map((x) => x.name);
// TODO: I may have broken some other languages with VarDeclaration change.
case "VarDeclaration":
return [node.variable.name];
case "FunctionDefinition":
return [node.name.name];
case "Identifier":
if (
spine.parent?.node.kind === "FunctionDefinition" &&
spine.getPathProp() === "args"
)
return [node.name];
return undefined;
}
}

Expand Down Expand Up @@ -180,11 +192,21 @@ function getTypeFromBinding(name: string, spine: Spine): Type {
);
return node.variable.type ?? assignedType;
}
default:
throw new Error(
`Programming error: node of type ${node.kind} does not bind any symbol`,
);
case "Identifier":
if (
spine.parent?.node.kind === "FunctionDefinition" &&
spine.getPathProp() === "args"
) {
if (node.type !== undefined) return node.type;
throw new PolygolfError(
`Programming error: function parameter '${node.name}' missing annotated type.`,
);
}
break;
}
throw new PolygolfError(
`Programming error: node of type ${node.kind} does not bind any symbol`,
);
}

export interface VarAccess {
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/parse-emit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ describe("Restricted nodes: parse - emit match", () => {
for (const t of [
`implicit_conversion "dec_to_int" "1";`,
`var_declaration $x:Int;`,
`func $x $x;`,
`var_declaration_with_assignment ($x:Int <- 0);`,
`var_declaration_block (var_declaration $x:Int) (var_declaration $y:Int);`,
`many_to_many_assignment {$x; $y} {"x"; "y"};`,
Expand Down Expand Up @@ -32,6 +33,11 @@ describe("Restricted nodes: parse - emit match", () => {
`for_c_like $i $c $a $body;`,
`named_arg "name" $x;`,
`1:1..1:"int";`,
`def_fn $f $x $y $body;`,
`def_fn_global $f $x $y $body;`,
`def_fn_expanded $f $x;`,
`def_fn_expanded_global $f $x;`,
// `scanning_macro_call $advance $x $y;`,
]) {
test(t.split(" ")[0], () => {
const normalized = normalize(t);
Expand Down
25 changes: 24 additions & 1 deletion src/frontend/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {
isIdent,
postfix,
type Text,
functionDefinition,
scanningMacroCall,
type OpCodeFrontName,
OpCodesUser,
OpCodeFrontNamesToOpCodes,
Expand Down Expand Up @@ -174,7 +176,7 @@ export function sexpr(
return keyValue(args[0], args[1]);
case "func": {
expectArity(1, Infinity);
const idents = args.slice(0, args.length);
const idents = args.slice(0, -1);
const expr = args[args.length - 1];
assertIdentifiers(idents);
return func(idents, expr);
Expand Down Expand Up @@ -376,6 +378,27 @@ export function sexpr(
case "named_arg":
expectArity(2);
return namedArg(asString(args[0]), args[1]);
case "def_fn":
case "def_fn_global":
case "def_fn_expanded":
case "def_fn_expanded_global": {
expectArity(2, Infinity);
const name = args[0];
assertIdentifier(name);
const idents = args.slice(1, -1);
const body = args[args.length - 1];
assertIdentifiers(idents);
const opts = {
isGlobal: callee.includes("global"),
isExpanded: callee.includes("expanded"),
};
return functionDefinition(name, idents, body, opts);
}
case "scanning_macro_call": {
expectArity(1, Infinity);
assertIdentifier(args[0]);
return scanningMacroCall(args[0], ...args.slice(1));
}
}
let matchingOpCodes = OpCodeFrontNames.includes(callee)
? OpCodeFrontNamesToOpCodes[callee as OpCodeFrontName]
Expand Down
Loading