Skip to content

Commit

Permalink
Ranges and for loops (#343)
Browse files Browse the repository at this point in the history
* add range opcodes

* add text_to_list opcodes

* transform some loop plugins

* fix getType

* add Literal type, use defaults in mapOps & gen'ed docs

* update emitters

* update parsing, polygolf emit

* remove some leftovers

* update loop plugins

* avoid circular dependency

* update docs

* fix couple tests

* add for lang tests

* rename concat alias to +

* remove ForEachKey, ForEachPair

* add lang names above fences

* add forEachToForRange plugin

* support ascii forEach overload

* use default values when parsing

* make .. backwards compatible

* fix 1 py test

* fix more tests

* add Cast node

* fix more tests

* add atTextToListToAtText plugin

* add py emit of naked text_to_list[codepoint]

* fix some tests

* fix gs range_diff_excl emit

* add test for range alias in py

* fix js loop over chars

* implement naked range in py

* fix last tests

* fix cover script

* fix naked range in nim

* fix argv in js

* update tests to use new for loops
  • Loading branch information
MichalMarsalek authored Mar 1, 2024
1 parent 3909bba commit 5d3b63c
Show file tree
Hide file tree
Showing 62 changed files with 1,642 additions and 1,258 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ Each variable must be first used in an assignment. Variable type is determined b
- `key_value`, `=>` - this can only be used as a part of a table literal.
- `func` - anonymous function literal - last argument is the body, all others are its arguments.
- `if` - if statement - expects a boolean condition and 1-2 bodies - a consequent and an optional alternate.
- `for` - a loop over an integer range - expects a loop variable, inclusive lower bound, exclusive upper bound, step and a body. If step is 1, it can be omitted, if in addition start is 0, it can be omitted, if in addition the loop variable is not needed, it can be omitted.
- `for` - a for each loop. Expects an optional loop variable, a list to iterate over and a body. To iterate over an integer range, construct a list using `..` or `..<`. Has the following syntactic sugars:
- `for $i $n body;` for `for $i (..<$n) body;`,
- `for $n body;` for `for (..<$n) body;`,
- `for $c $text body;` for `for $c (text_to_list[Ascii] $text) body`,
- `for[byte] $c $text body;` for `for $c (text_to_list[byte] $text) body`,
- `for[codepoint] $c $text body;` for `for $c (text_to_list[codepoint] $text) body`,
- `while` - a while loop. Expects a boolean condition and a body.
- `for_argv` - a loop over input arguments. Expects a loop variable and a static integer literal representing the upper bound on the number of arguments.
- `conditional` - a ternary conditional expression. Expects a boolean condition, a consequent and an alternate.
Expand All @@ -147,9 +152,6 @@ All other expressions are Polygolf operators. Most of them return values, but so
One can reference on opcode be either its name or its alias. Some opcodes share the alias - this is resolved by the used arity / types of inputs.
Symbolic aliases and `div`, `mod` can also be used in an infix manner: `(+ 2 3)` is the same as `(2 + 3)` and in mutating manner: `$x <- ($x + 1);` is the same as `$x +<- 1;`.

There's an alternative syntax for indexing assignment:
`($collection @ $index) <- value;` is the same as `set_at $collection $index $value;`.

Many text opcodes have `...[byte]`, `...[codepoint]`, `...[Ascii]` variants. Use the ascii one where possible, as that will allow target langs to choose any implementation. The other two will force implementations that are valid outside of the ascii range.

## Example
Expand Down
12 changes: 8 additions & 4 deletions docs/opcodes.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Hover opcode name to see a description.
| is_odd | [is_odd](## "Oddness predicate.") | [Int] | Bool |
| succ | [succ](## "Integer successor.") | [Int] | Int |
| pred | [pred](## "Integer predecessor.") | [Int] | Int |
| + | [add](## "Integer addition.") | [Int, Int, ...Int] | Int |
| + | [add](## "Integer addition.")<br>[append](## "Returns a new list with the given item appended at the end.")<br>[concat[List]](## "Returns a new list formed by concatenation of the inputs.")<br>[concat[Text]](## "Returns a new text formed by concatenation of the inputs.") | [Int, Int, ...Int]<br>[(List T1), T1]<br>[(List T1), (List T1), ...(List T1)]<br>[Text, Text, ...Text] | Int<br>(List T1)<br>(List T1)<br>Text |
| - | [sub](## "Integer subtraction.")<br>[neg](## "Integer negation.") | [Int, Int]<br>[Int] | Int<br>Int |
| * | [mul](## "Integer multiplication.") | [Int, Int, ...Int] | Int |
| div | [div](## "Integer floor division.") | [Int, Int] | Int |
Expand Down Expand Up @@ -58,6 +58,9 @@ Hover opcode name to see a description.
| char[byte] | [char[byte]](## "Returns a byte (as text) corresponding to the integer.") | [0..255] | (Text 1..1) |
| char[codepoint] | [char[codepoint]](## "Returns a codepoint (as text) corresponding to the integer.") | [0..1114111] | (Text 1..1) |
| char | [char[Ascii]](## "Returns a character corresponding to the integer.") | [0..127] | (Ascii 1..1) |
| text_to_list[byte] | [text_to_list[byte]](## "Converts given text to a list of single byte texts. Use for[byte] to iterate over bytes in a text.") | [Text] | (List (Text 1..1)) |
| text_to_list[codepoint] | [text_to_list[codepoint]](## "Converts given text to a list of single codepoint texts. Use for[codepoint] to iterate over codepoints in a text.") | [Text] | (List (Text 1..1)) |
| text_to_list | [text_to_list[Ascii]](## "Converts given text to a list of single character texts. Use for[Ascii] to iterate over characters in a text.") | [Ascii] | (List (Ascii 1..1)) |
| sorted | [sorted[Int]](## "Returns a sorted copy of the input.")<br>[sorted[Ascii]](## "Returns a lexicographically sorted copy of the input.") | [(List Int)]<br>[(List Ascii)] | (List Int)<br>(List Ascii) |
| reversed[byte] | [reversed[byte]](## "Returns a text in which the bytes are in reversed order.") | [Text] | Text |
| reversed[codepoint] | [reversed[codepoint]](## "Returns a text in which the codepoints are in reversed order.") | [Text] | Text |
Expand All @@ -70,13 +73,12 @@ Hover opcode name to see a description.
| size[codepoint] | [size[codepoint]](## "Returns the length of the text in codepoints.") | [Text] | 0..oo |
| size[byte] | [size[byte]](## "Returns the length of the text in bytes.") | [Text] | 0..2147483648 |
| include | [include](## "Modifies the set by including the given item.") | [(Set T1), T1] | Void |
| .. | [append](## "Returns a new list with the given item appended at the end.")<br>[concat[List]](## "Returns a new list formed by concatenation of the inputs.")<br>[concat[Text]](## "Returns a new text formed by concatenation of the inputs.") | [(List T1), T1]<br>[(List T1), (List T1), ...(List T1)]<br>[Text, Text, ...Text] | (List T1)<br>(List T1)<br>Text |
| repeat | [repeat](## "Repeats the text a given amount of times.") | [Text, 0..oo] | Text |
| split | [split](## "Splits the text by the delimiter.") | [Text, Text] | (List Text) |
| split_whitespace | [split_whitespace](## "Splits the text by any whitespace.") | [Text] | (List Text) |
| join | [join](## "Joins the items using the delimiter.") | [(List Text), Text] | Text |
| join | [join](## "Joins the items using the delimiter.") | [(List Text), Text = "";] | Text |
| right_align | [right_align](## "Right-aligns the text using spaces to a minimum length.") | [Text, 0..oo] | Text |
| replace | [replace](## "Replaces all occurences of a given text with another text.") | [Text, (Text 1..oo), Text] | Text |
| replace | [replace](## "Replaces all occurences of a given text with another text.") | [Text, (Text 1..oo), Text = "";] | Text |
| starts_with | [starts_with](## "Checks whether the second argument is a prefix of the first.") | [Text, Text] | Bool |
| ends_with | [ends_with](## "Checks whether the second argument is a suffix of the first.") | [Text, Text] | Bool |
| int_to_bin_aligned | [int_to_bin_aligned](## "Converts the integer to a 2-base text and alignes to a minimum length.") | [0..oo, 0..oo] | Ascii |
Expand All @@ -89,3 +91,5 @@ Hover opcode name to see a description.
| int_to_bool | [int_to_bool](## "Converts 0 to false and 1 to true.") | [0..1] | Bool |
| dec_to_int | [dec_to_int](## "Parses a integer from a 10-base text.") | [Ascii] | Int |
| bool_to_int | [bool_to_int](## "Converts false to 0 and true to 1.") | [Bool] | 0..1 |
| .. | [range_incl](## "List of integers between given inclusive bounds, with given step.") | [Int = 0;, Int, 1..oo = 1;] | (List Int) |
| ..< | [range_excl](## "List of integers between given inclusive lower, exclusive upper bound, with given step.") | [Int = 0;, Int, 1..oo = 1;] | (List Int) |
51 changes: 20 additions & 31 deletions src/IR/IR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,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,
Cast,
} from "./exprs";
import {
type ForRange,
type ForEach,
type ForEachKey,
type ForEachPair,
type ForCLike,
type While,
type ForArgv,
type ForDifferenceRange,
} from "./loops";
import { type ForEach, type ForCLike, type While, type ForArgv } from "./loops";
import {
type AnyInteger,
type Identifier,
Expand Down Expand Up @@ -84,8 +76,8 @@ export type Node =
| Set
| Table
| ConditionalOp
| ForEach
| While
| ForRange
| ForArgv
| If
// Other nodes
Expand All @@ -103,12 +95,9 @@ export type Node =
| Prefix
| Postfix
| Import
| ForDifferenceRange
| ForEach
| ForEachKey
| ForEachPair
| ForCLike
| NamedArg;
| NamedArg
| Cast;

export type NodeFuncRecord<Tout, Tin extends Node = Node> = Tin extends Node
? Record<Tin["kind"], (n: Tin, s: Spine<Tin>) => Tout>
Expand Down
4 changes: 2 additions & 2 deletions src/IR/associativeOpsRepr.test.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ print_int 0;
```polygolf
$a:Text <- "x";
$b:Text <- "x";
print (($a .. "abc") .. ("def" .. $b));
print (($a + "abc") + ("def" + $b));
```

```polygolf nogolf
$a:Text <- "x";
$b:Text <- "x";
print (.. $a "abcdef" $b);
print (+ $a "abcdef" $b);
```
79 changes: 62 additions & 17 deletions src/IR/collections.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,94 @@
import { type BaseNode, type Node, type KeyValue } from "./IR";
import {
type BaseNode,
type Node,
type KeyValue,
type Integer,
type Text,
isOfKind,
} from "./IR";

export interface Array extends BaseNode {
export interface Array<T extends Node = Node> extends BaseNode {
readonly kind: "Array";
readonly exprs: readonly Node[];
readonly value: readonly T[];
}

export interface List extends BaseNode {
export interface List<T extends Node = Node> extends BaseNode {
readonly kind: "List";
readonly exprs: readonly Node[];
readonly value: readonly T[];
}

export interface Set extends BaseNode {
export interface Set<T extends Node = Node> extends BaseNode {
readonly kind: "Set";
readonly exprs: readonly Node[];
readonly value: readonly T[];
}

export interface Table extends BaseNode {
export interface Table<Key extends Node = Node, Value extends Node = Node>
extends BaseNode {
readonly kind: "Table";
readonly kvPairs: readonly KeyValue[];
readonly value: readonly KeyValue<Key, Value>[];
}

export function array(exprs: readonly Node[]): Array {
export type Literal =
| Integer
| Text
| Array<Literal>
| List<Literal>
| Set<Literal>
| KeyValue<Literal, Literal>
| Table<Literal, Literal>;

export function isLiteral(x: Node): x is Literal {
return (
isOfKind("Integer", "Text")(x) ||
(isOfKind("List", "Array", "Set", "Table")(x) &&
x.value.every(isLiteral)) ||
(x.kind === "KeyValue" && isLiteral(x.key) && isLiteral(x.value))
);
}

export function isEqualToLiteral(x: Node, literal: Literal): boolean {
if (isOfKind("Integer", "Text")(literal)) {
return isOfKind(literal.kind)(x) && x.value === literal.value;
} else if (isOfKind("Array", "List", "Set", "Table")(literal)) {
return (
isOfKind(literal.kind)(x) &&
x.value.every((x, i) => isEqualToLiteral(x, literal.value[i]))
);
}
if (isOfKind("KeyValue")(literal)) {
return (
isOfKind(literal.kind)(x) &&
isEqualToLiteral(x.key, literal.key) &&
isEqualToLiteral(x.value, literal.value)
);
}
throw new Error("Unknown literal kind.");
}

export function array(value: readonly Node[]): Array {
return {
kind: "Array",
exprs,
value,
};
}

export function list(exprs: readonly Node[]): List {
export function list(value: readonly Node[]): List {
return {
kind: "List",
exprs,
value,
};
}

export function set(exprs: readonly Node[]): Set {
export function set(value: readonly Node[]): Set {
return {
kind: "Set",
exprs,
value,
};
}

export function table(kvPairs: readonly KeyValue[]): Table {
export function table(value: readonly KeyValue[]): Table {
return {
kind: "Table",
kvPairs,
value,
};
}
46 changes: 37 additions & 9 deletions src/IR/exprs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ import {
opCodeDefinitions,
isNullary,
OpCodes,
defaults,
maxArity,
} from "./IR";
import { mapObjectValues } from "../common/arrays";
import { mapObjectValues, useDefaults } from "../common/arrays";

export interface ImplicitConversion extends BaseNode {
readonly kind: "ImplicitConversion";
expr: Node;
behavesLike: UnaryOpCode & `${string}_to_${string}`;
}

export interface Cast extends BaseNode {
readonly kind: "Cast";
expr: Node;
}

/**
* All expressions start as a `Op` node.
* This node is used to represent an abstract operation.
Expand All @@ -60,10 +67,11 @@ export interface Op<Op extends OpCode = OpCode> extends BaseNode {
readonly args: OpCodeArgValues<Op>;
}

export interface KeyValue extends BaseNode {
export interface KeyValue<Key extends Node = Node, Value extends Node = Node>
extends BaseNode {
readonly kind: "KeyValue";
readonly key: Node;
readonly value: Node;
readonly key: Key;
readonly value: Value;
}

export interface FunctionCall extends BaseNode {
Expand Down Expand Up @@ -155,6 +163,10 @@ export function implicitConversion(
};
}

export function cast(expr: Node, targetType?: string): Cast {
return { kind: "Cast", expr, targetType };
}

export function keyValue(key: Node, value: Node): KeyValue {
return {
kind: "KeyValue",
Expand Down Expand Up @@ -183,7 +195,10 @@ export const op = {
? Op<O>
: (...args: OpCodeArgValues<O>) => Op<O>;
}),
unsafe: opUnsafe,
unsafe(op: OpCode, useDefaults = false) {
return (...args: Node[]) =>
(useDefaults ? opUnsafeWithDefaults : opUnsafe)(op, ...args);
},
} as const;

/**
Expand Down Expand Up @@ -223,9 +238,7 @@ function opUnsafe(opCode: OpCode, ...args: Node[]): Node {
if (arg.op in booleanNotOpCode) {
return op.unsafe(
booleanNotOpCode[arg.op as keyof typeof booleanNotOpCode],
arg.args[0]!,
arg.args[1]!,
);
)(arg.args[0]!, arg.args[1]!);
}
}
}
Expand Down Expand Up @@ -286,7 +299,7 @@ function opUnsafe(opCode: OpCode, ...args: Node[]): Node {
isInt()(x)
? int(-x.value)
: x === toNegate
? op.unsafe("add", ...(x as Op).args.map(op.neg))
? op.unsafe("add")(...(x as Op).args.map(op.neg))
: x,
);
}
Expand Down Expand Up @@ -317,6 +330,21 @@ function opUnsafe(opCode: OpCode, ...args: Node[]): Node {
return _op(opCode, ...args);
}

export function opArgsWithDefaults(
opCode: OpCode,
args: readonly Node[],
): readonly Node[] {
const targetArity = maxArity(opCode);
if (targetArity !== Infinity) {
return useDefaults(targetArity, defaults[opCode] ?? [], args);
}
return args;
}

function opUnsafeWithDefaults(opCode: OpCode, ...args: Node[]): Node {
return opUnsafe(opCode, ...opArgsWithDefaults(opCode, args));
}

function evalBinary(
op: BinaryOpCode | VariadicOpCode,
left: Node,
Expand Down
Loading

0 comments on commit 5d3b63c

Please sign in to comment.