Skip to content

Commit

Permalink
Merge pull request #1664 from bbrk24/range-efficiency
Browse files Browse the repository at this point in the history
Range literal improvements: faster, fix doubly strict
  • Loading branch information
edemaine authored Dec 29, 2024
2 parents 137d4c1 + 95683a6 commit d1879d6
Show file tree
Hide file tree
Showing 5 changed files with 448 additions and 54 deletions.
32 changes: 19 additions & 13 deletions source/parser/for.civet
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ function processRangeExpression(start: ASTNode, ws1: ASTNode, range: RangeDots,
Math.abs
lengthAdjust :=
1 - Number(not range.left.inclusive) - Number(not range.right.inclusive)
lengthAdjustExpression :=
if lengthAdjust > 0 then ` + ${lengthAdjust}`
else if lengthAdjust < 0 then ` - ${-lengthAdjust}`

let children: Children?
if start is like {type: "Literal"} and end is like {type: "Literal"}
Expand All @@ -75,9 +72,12 @@ function processRangeExpression(start: ASTNode, ws1: ASTNode, range: RangeDots,
. "]"
else
children =
. `Array.from({length: ${length.toString()}}, `
. "(_, i) => String.fromCharCode(", startCode.toString()
. step > 0 ? " + " : " - ", "i))"
. getHelperRef startCode <= endCode ? "stringRange" : "revStringRange"
. "("
. startCode.toString()
. ", "
. length.toString()
. ")"
children.unshift range.error if range.error?
else if startValue <? "number" and endValue <? "number"
step := startValue <= endValue ? 1 : -1
Expand All @@ -97,21 +97,27 @@ function processRangeExpression(start: ASTNode, ws1: ASTNode, range: RangeDots,
sign := range.increasing ? "+" : "-"
end = makeLeftHandSideExpression end
children =
. "((s) => Array.from({length: "
. range.increasing ? [ws2, end, " - s"] : ["s - ", ws2, end]
. lengthAdjustExpression
. "}, (_, i) => s ", sign, " i))"
. getHelperRef range.increasing ? 'range' : 'revRange'
. "("
. if range.left.inclusive
start
else
[makeLeftHandSideExpression(start), ` ${sign} 1`]
. ","
. if range.right.inclusive
[makeLeftHandSideExpression(end), ` ${sign} 1`]
else
end
. ...ws1, ")"
else
children =
. "((s, e) => {let step = e > s ? 1 : -1; return Array.from({length: Math.abs(e - s)"
. lengthAdjustExpression
. "}, (_, i) => s + i * step)})"
. "((s, e) => s > e ? "
. getHelperRef("revRange"), "(s, e"
. if range.right.inclusive then " - 1"
. ") : "
. getHelperRef("range"), "(s, e"
. if range.right.inclusive then " + 1"
. "))"
. "(", start, ...ws1, comma, ws2, end, ")"

{
Expand Down
98 changes: 90 additions & 8 deletions source/parser/helper.civet
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@ declareHelper := {
}
"\n"
]]
rslice(rsliceRef): void
RSliceable := makeRef "RSliceable"
RSliceable(RSliceableRef): void
state.prelude.push ["",
ts [ "type ", RSliceable, "<R> = string | {length: number; slice(start: number, end: number): {reverse(): R}}\n" ]
ts [ "type ", RSliceableRef, "<R> = string | {length: number; slice(start: number, end: number): {reverse(): R}}\n" ]
]
rslice(rsliceRef): void
RSliceableRef := getHelperRef "RSliceable"
state.prelude.push ["", [
preludeVar
rsliceRef
ts [ ": <R, T extends string | ", RSliceable, "<R>>(a: T, start?: number, end?: number) => T extends string ? string : T extends ", RSliceable, "<infer R> ? R : never" ]
ts [ ": <R, T extends string | ", RSliceableRef, "<R>>(a: T, start?: number, end?: number) => T extends string ? string : T extends ", RSliceableRef, "<infer R> ? R : never" ]
" = ((a, start = -1, end = -1) => {\n"
" const l = a.length\n"
" if (start < 0) start += l\n"
Expand All @@ -87,6 +88,76 @@ declareHelper := {
" }\n"
"})"
], ";\n"]
range(rangeRef): void
state.prelude.push ["", [
preludeVar
rangeRef
ts [ ": (start: number, end: number) => number[]" ]
" "
"""
= (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
}
"""
], ";\n"]
revRange(revRangeRef): void
state.prelude.push ["", [
preludeVar
revRangeRef
ts [ ": (start: number, end: number) => number[]" ]
" "
"""
= (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
}
"""
], ";\n"]
stringRange(stringRangeRef): void
state.prelude.push ["", [
preludeVar
stringRangeRef
ts [ ": (start: number, length: number) => string[]" ]
" "
"""
= (start, length) => {
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = String.fromCharCode(start + i);
}
return arr;
}
"""
], ";\n"]
revStringRange(revStringRangeRef): void
state.prelude.push ["", [
preludeVar
revStringRangeRef
ts [ ": (start: number, length: number) => string[]" ]
" "
"""
= (start, length) => {
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = String.fromCharCode(start - i);
}
return arr;
}
"""
], ";\n"]
div(divRef): void
state.prelude.push ["", [ // [indent, statement]
preludeVar
Expand Down Expand Up @@ -194,10 +265,21 @@ function peekHelperRef(base: HelperName): ASTRef?
* Extract a subarray of the prelude array that just contains the
* helper functions used in the given subtree
*/
function extractPreludeFor(node: ASTNode): ASTNode[]
helpers .= new Set Object.values state.helperRefs
helpers = new Set gatherRecursive node, helpers@has
state.prelude.filter (s) => (gatherRecursive s, helpers@has)#
function extractPreludeFor(node: ASTNode): StatementTuple[]
return state.prelude unless state.prelude#
allHelpers := new Set Object.values state.helperRefs
isHelper := allHelpers@has as (node: ASTNode) => node is ASTRef
usedHelpers := new Set gatherRecursive node, isHelper
// Find helpers used within helpers
loop
prelude .= state.prelude.filter (s) =>
(gatherRecursive s, usedHelpers@has as (node: ASTNode) => node is ASTRef)#
changed .= false
for each helper of gatherRecursive prelude, isHelper
unless usedHelpers.has helper
usedHelpers.add helper
changed = true
return prelude unless changed

export {
extractPreludeFor
Expand Down
10 changes: 7 additions & 3 deletions source/parser/lib.civet
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ import {
} from ./unary.civet
import { createConstLetDecs, createVarDecs } from ./auto-dec.civet
import { expressionizeComptime, processComptime } from ./comptime.civet
import { getHelperRef, peekHelperRef } from ./helper.civet
import {
extractPreludeFor
getHelperRef
peekHelperRef
} from ./helper.civet

import {
dedentBlockString
Expand Down Expand Up @@ -1590,8 +1594,8 @@ function processProgram(root: BlockStatement): void

processCoffeeClasses(statements) if config.coffeeClasses

// Insert prelude
statements.unshift(...state.prelude)
// Insert prelude, dropping any actually unused helpers
statements.unshift ...extractPreludeFor statements

if config.autoLet
createConstLetDecs(statements, [], "let")
Expand Down
114 changes: 109 additions & 5 deletions test/compat/coffee-range.civet
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
{ testCase } from "../helper.civet"
{ testCase, evalsTo } from "../helper.civet"

describe "coffeeRange", ->
it "coffeeRange behaves like range", ->
evalsTo """
"civet coffeeRange"
[0..100]
""", [0..100]

evalsTo """
"civet coffeeRange"
[0...100]
""", [0...100]

evalsTo """
"civet coffeeRange"
[100..0]
""", [100>=..>=0]

evalsTo """
"civet coffeeRange"
[100...0]
""", [100>=..>0]

evalsTo """
"civet coffeeRange"
[' '..'~']
""", [' '..'~']

evalsTo """
"civet coffeeRange"
[' '...'~']
""", [' '...'~']

evalsTo """
"civet coffeeRange"
['~'..' ']
""", ['~'>=..>=' ']

evalsTo """
"civet coffeeRange"
['~'...' ']
""", ['~'>=..>' ']

testCase """
[0..10]
---
Expand Down Expand Up @@ -34,7 +75,25 @@ describe "coffeeRange", ->
"civet coffeeRange"
[0...256]
---
((s, e) => {let step = e > s ? 1 : -1; return Array.from({length: Math.abs(e - s)}, (_, i) => s + i * step)})(0,256)
var revRange: (start: number, end: number) => number[] = (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
};
var range: (start: number, end: number) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
((s, e) => s > e ? revRange(s, e) : range(s, e))(0,256)
"""

testCase """
Expand All @@ -43,7 +102,25 @@ describe "coffeeRange", ->
"civet coffeeRange"
[0..255]
---
((s, e) => {let step = e > s ? 1 : -1; return Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + i * step)})(0,255)
var revRange: (start: number, end: number) => number[] = (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
};
var range: (start: number, end: number) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
((s, e) => s > e ? revRange(s, e - 1) : range(s, e + 1))(0,255)
"""

testCase """
Expand All @@ -69,7 +146,25 @@ describe "coffeeRange", ->
"civet coffeeRange"
[x - 5 .. x + 5]
---
((s, e) => {let step = e > s ? 1 : -1; return Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + i * step)})(x - 5 , x + 5)
var revRange: (start: number, end: number) => number[] = (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
};
var range: (start: number, end: number) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
((s, e) => s > e ? revRange(s, e - 1) : range(s, e + 1))(x - 5 , x + 5)
"""

testCase """
Expand Down Expand Up @@ -127,5 +222,14 @@ describe "coffeeRange", ->
"civet coffeeRange"
[a..<b]
---
((s) => Array.from({length: b - s}, (_, i) => s + i))(a)
var range: (start: number, end: number) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
range(a,b)
"""
Loading

0 comments on commit d1879d6

Please sign in to comment.