diff --git a/docs/kcl/map.md b/docs/kcl/map.md index 326628788..52e90cee3 100644 --- a/docs/kcl/map.md +++ b/docs/kcl/map.md @@ -37,7 +37,7 @@ fn drawCircle = (id) => { // Call `drawCircle`, passing in each element of the array. // The outputs from each `drawCircle` form a new array, // which is the return value from `map`. -circles = map([1, 2, 3], drawCircle) +circles = map([1..3], drawCircle) ``` ![Rendered example of map 0](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQCAYAAADPfd1WAACGMklEQVR4Ae3AA6AkWZbG8f937o3IzKdyS2Oubdu2bdu2bdu2bWmMnpZKr54yMyLu+Xa3anqmhztr1a8+6EEP4qqrrrrqqquuuuqqq6666qqrrrrqqquu+j+JylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVV/4ddc801D+YBzpw582D+jc6ePXsrz3TffffdylVX/Q9yzTXXPJgHOHPmzIP5Nzp79uytPNN99913K1dd9T/INddc82Ae4MyZMw/m3+js2bO38kz33XffrVx11VVXXXXV/01Urrrqqquuuup/gWuuuebBAGfOnHnwNddc8+AzZ848COCaa6558DXXXPNgnunMmTMPvuaaax7Mf5H77rvvVp7p7Nmzt9533323Atx33323nj179hkA9913361nz5699b777ruVq676F1xzzTUPBjhz5syDr7nmmgefOXPmQQDXXHPNgwGuueaaBwOcOXPmwddcc82D+S9y33333coznT179tb77rvvVoD77rvv1rNnzz4D4L777rsV4B/+4R9+m6uuehFcc801Dz5z5syDr7nmmgefOXPmQQDXXHPNgwGuueaaBwNcc801Dzlz5syD+C9y33333QogSffdd9/TAe67775bAe67775bz549+4x/+Id/+O0zZ848+OzZs7fed999t3LVVVddddVV/7OhBz3oQVx11VVXXXXVf6drrrnmwWfOnHnwNddc8+AzZ848CODFX/zFXxvgxV7sxV6bF+B3f/d3v4v/wV7zNV/zfXgu9913360A//AP//DbAP/wD//wO/fdd9+tZ8+evfW+++67lav+z7vmmmsefObMmQdfc801Dz5z5syDrrnmmgdfc801Dz5z5syDr7nmmgfzfPzu7/7ud/E/2Gu+5mu+D8/lvvvuuxXg7Nmzt95333233nfffbeePXv2Gffdd9+tZ8+evfW+++67lav+z7vmmmsefObMmQe/2Iu92GsBXHPNNQ++5pprHvxiL/Zir83z8bu/+7vfxf9gr/mar/k+PB/33XffrQBnz5699b777rv1vvvuu/Uf/uEffgfg7Nmzt9533323ctVVV1111VX/fdCDHvQgrrrqqquuuuo/2zXXXPPgM2fOPPiaa6558JkzZx704i/+4q8N8GIv9mKvzXP53d/93e/i/4nXfM3XfB+e6b777rsV4B/+4R9++x/+4R9+57777rv17Nmzt9533323ctX/Otdcc82DX+zFXuy1z5w586Brrrnmwddcc82DX+zFXuy1eS6/+7u/+138P/Gar/ma78Mz3XfffbcC/MM//MNvA/zDP/zD79x33323/sM//MNvc9X/Otdcc82Dz5w58+AXe7EXe61rrrnmIddcc82DXuzFXuy1eYDf/d3f/S7+n3jN13zN9+EB7rvvvlsl6b777nv6fffdd+s//MM//M59991369mzZ2+97777buWqq6666qqr/nOhBz3oQVx11VVXXXXVf5RrrrnmwWfOnHnwi73Yi70WwIu/+Iu/9ou92Iu9Ng/wu7/7u9/FVS/Ua77ma74Pz3Tffffdevbs2Vvvu+++W//hH/7hd/7hH/7ht++7775buep/hGuuuebBZ86cefCLvdiLvdY111zz4Bd7sRd77WuuuebBPNPv/u7vfhdX/Yte8zVf8314pvvuu+/W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2mu+5mu+z4d8yIc85B/+4R9+m2e67777bv2Hf/iH3+aqq676N/mHf/iH3+aZzp49+4zP/MzPfJ3XfM3XfB+uuuqqf7UXf/EXf22uuuqqq64iuOqqq666ijNnzjyYq6666l/lNV/zNd/n67/+69/nvvvuu5Xn8vd///e//Zqv+Zrvw1VXXfUie83XfM33+a3f+q3v5rn8wz/8w2//yI/8yGc/+tGPfh2uuuqqf5UzZ848mKuuuuqqqwiuuuqqq6666qqr/pVe8zVf831+5Ed+5LN/67d+67t5Pn77t3/7e7jqqqv+Ve67775bf/RHf/RzeD5+9Ed/9HN+67d+67tf8zVf83246qqrrrrqqquu+tchuOqqq666imuuuebBXHXVVS+S13zN13yfH/mRH/nsH/3RH/0cXoD77rvv1h/5kR/57Nd8zdd8H6666qoXyT/8wz/8zn333XcrL8CP/uiPfs6P/MiPfPZrvuZrvg9XXXXVVVddddVVLzqCq6666qqruO+++27lqquu+he95mu+5vt8/dd//fv86I/+6OfwL/jt3/7t7+Gqq656kbzma77m+/zoj/7oZ/Mv+NEf/dHP+ZEf+ZHPfs3XfM334aqrrrrqqquuuupFQ3DVVVdddRVnz569lauuuuqFes3XfM33+fqv//r3+a3f+q3v5kVw33333fr1X//17/Oar/ma78NVV131Ar3ma77m+/zIj/zIZ99333238iL40R/90c/5kR/5kc9+zdd8zffhqquueqHOnj17K1ddddVVV1GOHz/OVVddddX/dy/2Yi/22hHBVVdd9fy95mu+5vt85md+5uv86Z/+6U/zr3B0dLT7kIc85KWXy+UuV1111fN1cHBw6eu//uvfh3+Ff/iHf/idf/iHf/id937v9/7qZzzjGX/NVVdd9Xw97WlP+5t/+Id/+B2uuuqqq/5/I7jqqquuuop/+Id/+J3XfM3XfB+uuuqq5/Car/ma7/PoRz/6dT7zMz/zdf7hH/7ht/lXuu+++279+q//+vd5zdd8zffhqquueh6v+Zqv+T4/8iM/8jn8G/zDP/zDb3/Ih3zIQx796Ee/zmu+5mu+D1ddddVzeM3XfM33OXv27DO46qqrrrqK4KqrrrrqKv7hH/7ht7nqqquew2u+5mu+zz/8wz/89od8yIc85B/+4R9+m3+j++6779av//qv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qquu+u+GHvSgB3HVVVddddX/LNdcc82DX+zFXuy1X+zFXuy1rrnmmge/2Iu92GsD/O7v/u53cdV/utd8zdd8H57pvvvuu/Xs2bO3/v3f//1v/8M//MPv/MM//MNvc9X/O9dcc82DX+zFXuy1X+zFXuy1rrnmmge/2Iu92GvzTL/7u7/7XVz1n+41X/M134dnuu+++279rd/6re/+h3/4h9/5h3/4h9/mqv93zpw586BrrrnmIddcc82DX+zFXuy1rrnmmge/2Iu92GvzTL/7u7/7XVz1n+41X/M13wfgH/7hH3777//+738b4Ed/9Ec/h6uuuuqqq/6nQQ960IO46qqrrrrqf7ZrrrnmwS/2Yi/22i/2Yi/2Wtdcc82DX+zFXuy1eabf/d3f/S6u+jd5zdd8zffhAe67775b/+Ef/uG377vvvlv/4R/+4Xf+4R/+4be56qrn48yZMw+SpBd7sRd77Rd7sRd7rWuuuebBL/ZiL/baPNPv/u7vfhdX/Zu95mu+5vvwTPfdd9+tv/Vbv/XdAP/wD//wO//wD//w21x11fNx5syZB11zzTUPueaaax78Yi/2Yq91zTXXPPjFXuzFXpsH+N3f/d3v4qp/k9d8zdd8H57pvvvuu/W3fuu3vhvgR3/0Rz+Hq6666qqr/qdDD3rQg7jqqquuuup/l2uuuebBZ86cefA111zz4Bd7sRd7rWuuuebBL/ZiL/baPMDv/u7vfhdX8Zqv+ZrvwwPcd999twL8wz/8w2/fd999t/7DP/zD75w9e/bW++6771auuurf4cyZMw+SpBd7sRd77Rd7sRd7rWuuuebBZ86cefA111zzYB7gd3/3d7+Lq3jN13zN9+EB7rvvvlvPnj1769///d//NsA//MM//M4//MM//DZXXfXvdM011zz4xV7sxV4b4MVe7MVe65prrnnwi73Yi702D/C7v/u738VVl73ma77m+/AA//AP//Dbf//3f//bAP/wD//wO//wD//w21x11VVXXfW/DXrQgx7EVVddddVV/zdcc801D36xF3ux1wZ4sRd7sde65pprHnzmzJkHX3PNNQ/mAX73d3/3u/g/4jVf8zXfh+dy33333Xr27Nlb//7v//63Ac6ePfuM++6779Z/+Id/+G2uuuq/2DXXXPNggBd7sRd7bYAXe7EXe61rrrnmwWfOnHnwNddc82Cey+/+7u9+F/8HvOZrvub78Fzuu+++W8+ePXvrfffdd+t9991369mzZ59x33333foP//APv81VV/0Xu+aaax585syZB19zzTUPPnPmzIOuueaaB19zzTUPPnPmzIOvueaaB/Ncfvd3f/e7+D/iNV/zNd+HB7jvvvtuPXv27K333Xffrffdd9+t//AP//A7Z8+evfW+++67lauuuuqqq/4vQA960IO46qqrrrrq/7ZrrrnmwQBnzpx58DXXXPPgF3uxF3stgGuuuebBAC/2Yi/22rwQf/qnf/pj/Bd4xVd8xXfghbjvvvtuBTh79uyt991336333XffrQBnz559xn333Xfr2bNnb73vvvtu5aqr/pe45pprHgxw5syZB11zzTUPAXixF3ux17rmmmseDHDmzJkHX3PNNQ/mhfjTP/3TH+O/wCu+4iu+Ay/EP/zDP/z2mTNnHnz27Nlb//7v//63Ac6ePfuM++677+lnz559xn333XcrV131v8Q111zzYNu+5pprHnLNNdc8GODFXuzFXgvgmmuueTDAmTNnHnzNNdc8mBfiT//0T3+M/2Sv+Iqv+A68EPfdd9+tAGfPnr31vvvuu/W+++67FeC3f/u3v+fMmTMP/od/+Iff5qqrrrrqqv/r0IMe9CCuuuqqq666CuCaa655MMCZM2cefM011zyYZzpz5syDrrnmmgfzXK655poH81zOnDnzYB7g7Nmzt/J83HfffbfyTPfdd9+tPNPZs2efAXDffffdCnD27Nlb77vvvlu56qr/x6655poHA9j2Nddc85BrrrnmwTybX+zFXuy1eS7XXHPNg3k+zpw582Ce6ezZs7cC3Hfffbdec801D+aZ7rvvvlsB7rvvvlt5gLNnzz4D4L777rsV8NmzZ59x33333cpVV/0/dubMmQdJ0pkzZx4McM011zwY8JkzZx4McM011zyY53LNNdc8mOdy5syZB/MAZ8+evZXn47777ruVZ7rvvvtu5ZnOnj37DMD33XffMwD+4R/+4be56qqrrrrqqivQgx70IK666qqrrrrqqquuuuqqq6666qqrrrrqqv+TqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8Vlauuuuqqq6666qqrrrrqqquuuuqqq6666v8qKlddddVVV1111VVXXXXVVVddddVVV1111f9VVK666qqrrrrqqquuuuqqq6666qqrrrrqqv+rqFx11VVXXXXVVVddddVVV1111VVXXXXVVf9XUbnqqquuuuqqq6666qqrrrrqqquuuuqqq/6vonLVVVddddVVV1111VVXXXXVVVddddVVV/1fReWqq6666qqrrrrqqquuuuqqq6666qqrrvq/ispVV1111VVXXXXVVVddddVVV1111VVXXfV/FZWrrrrqqquuuuqqq6666qqrrrrqqquuuur/KipXXXXVVVddddVVV1111VVXXXXVVVddddX/VVSuuuqqq6666qqrrrrqqquuuuqqq6666qr/q6hcddVVV1111VVXXXXVVVddddVVV1111VX/V1G56qqrrrrqqquuuuqqq6666qqrrrrqqqv+r6Jy1VVXXXXVVVddddVVV1111VVXXXXVVVf9X0Xlqquuuuqqq6666qqrrrrqqquuuuqqq676v4rKVVddddVVV1111VVXXXXVVVddddVVV131fxWVq6666qqrrrrqqquuuuqqq6666qqrrrrq/yoqV1111VVXXXXVVVddddVVV1111VVXXXXV/1VUrrrqqquuuuqqq6666qqrrrrqqquuuuqq/6uoXHXVVVddddVVV1111VVXXXXVVVddddVV/1dRueqqq6666qqrrrrqqquuuuqqq6666qqr/q+ictVVV1111VVXXXXVVVddddVVV1111VVX/V9F5aqrrrrqqquuuuqqq6666qqrrrrqqquu+r+KylVXXXXVVVddddVVV1111VVXXXXVVVdd9X8V/wiJYjdDRauFNwAAAABJRU5ErkJggg==) @@ -45,7 +45,7 @@ circles = map([1, 2, 3], drawCircle) ```js r = 10 // radius // Call `map`, using an anonymous function instead of a named one. -circles = map([1, 2, 3], (id) => { +circles = map([1..3], (id) => { return startSketchOn("XY") |> circle({ center: [id * 2 * r, 0], radius: r }, %) }) diff --git a/docs/kcl/reduce.md b/docs/kcl/reduce.md index 70de7bfdf..a50e8223a 100644 --- a/docs/kcl/reduce.md +++ b/docs/kcl/reduce.md @@ -32,7 +32,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue fn decagon = (radius) => { step = 1 / 10 * tau() sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius]) - return reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], sketch001, (i, sg) => { + return reduce([1..10], sketch001, (i, sg) => { x = cos(step * i) * radius y = sin(step * i) * radius return lineTo([x, y], sg) diff --git a/docs/kcl/std.json b/docs/kcl/std.json index 70927f0bc..d43fdbe01 100644 --- a/docs/kcl/std.json +++ b/docs/kcl/std.json @@ -83233,6 +83233,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -86831,6 +86881,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -90433,6 +90533,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -91704,8 +91854,8 @@ "unpublished": false, "deprecated": false, "examples": [ - "r = 10 // radius\nfn drawCircle = (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1, 2, 3], drawCircle)", - "r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1, 2, 3], (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n})" + "r = 10 // radius\nfn drawCircle = (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)", + "r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n})" ] }, { @@ -114887,6 +115037,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -118878,6 +119078,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -122476,6 +122726,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -126072,6 +126372,56 @@ } } }, + { + "type": "object", + "required": [ + "end", + "endElement", + "endInclusive", + "start", + "startElement", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "ArrayRangeExpression" + ] + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "startElement": { + "$ref": "#/components/schemas/Expr" + }, + "endElement": { + "$ref": "#/components/schemas/Expr" + }, + "endInclusive": { + "description": "Is the `end_element` included in the range?", + "type": "boolean" + }, + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32, + "nullable": true + } + } + }, { "type": "object", "required": [ @@ -127739,7 +128089,7 @@ "unpublished": false, "deprecated": false, "examples": [ - "fn decagon = (radius) => {\n step = 1 / 10 * tau()\n sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])\n return reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], sketch001, (i, sg) => {\n x = cos(step * i) * radius\n y = sin(step * i) * radius\n return lineTo([x, y], sg)\n})\n}\ndecagon(5.0)\n |> close(%)", + "fn decagon = (radius) => {\n step = 1 / 10 * tau()\n sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])\n return reduce([1..10], sketch001, (i, sg) => {\n x = cos(step * i) * radius\n y = sin(step * i) * radius\n return lineTo([x, y], sg)\n})\n}\ndecagon(5.0)\n |> close(%)", "array = [1, 2, 3]\nsum = reduce(array, 0, (i, result_so_far) => {\n return i + result_so_far\n})\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")", "fn add = (a, b) => {\n return a + b\n}\nfn sum = (array) => {\n return reduce(array, 0, add)\n}\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")" ] diff --git a/docs/kcl/types/Expr.md b/docs/kcl/types/Expr.md index b3e70b63b..6f708b237 100644 --- a/docs/kcl/types/Expr.md +++ b/docs/kcl/types/Expr.md @@ -197,6 +197,27 @@ An expression can be evaluated to yield a single KCL value. +## Properties + +| Property | Type | Description | Required | +|----------|------|-------------|----------| +| `type` |enum: `ArrayRangeExpression`| | No | +| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No | +| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No | +| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | +| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | +| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No | +| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No | + + +---- + +**Type:** `object` + + + + + ## Properties | Property | Type | Description | Required | diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index 16f1a52e8..b39b3cacc 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -513,6 +513,7 @@ pub enum Expr { PipeExpression(Box), PipeSubstitution(Box), ArrayExpression(Box), + ArrayRangeExpression(Box), ObjectExpression(Box), MemberExpression(Box), UnaryExpression(Box), @@ -532,6 +533,7 @@ impl Expr { Expr::PipeExpression(pe) => pe.compute_digest(), Expr::PipeSubstitution(ps) => ps.compute_digest(), Expr::ArrayExpression(ae) => ae.compute_digest(), + Expr::ArrayRangeExpression(are) => are.compute_digest(), Expr::ObjectExpression(oe) => oe.compute_digest(), Expr::MemberExpression(me) => me.compute_digest(), Expr::UnaryExpression(ue) => ue.compute_digest(), @@ -569,6 +571,7 @@ impl Expr { match self { Expr::BinaryExpression(_bin_exp) => None, Expr::ArrayExpression(_array_exp) => None, + Expr::ArrayRangeExpression(_array_exp) => None, Expr::ObjectExpression(_obj_exp) => None, Expr::MemberExpression(_mem_exp) => None, Expr::Literal(_literal) => None, @@ -593,6 +596,7 @@ impl Expr { match self { Expr::BinaryExpression(ref mut bin_exp) => bin_exp.replace_value(source_range, new_value), Expr::ArrayExpression(ref mut array_exp) => array_exp.replace_value(source_range, new_value), + Expr::ArrayRangeExpression(ref mut array_range) => array_range.replace_value(source_range, new_value), Expr::ObjectExpression(ref mut obj_exp) => obj_exp.replace_value(source_range, new_value), Expr::MemberExpression(_) => {} Expr::Literal(_) => {} @@ -619,6 +623,7 @@ impl Expr { Expr::PipeExpression(pipe_expression) => pipe_expression.start(), Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.start(), Expr::ArrayExpression(array_expression) => array_expression.start(), + Expr::ArrayRangeExpression(array_range) => array_range.start(), Expr::ObjectExpression(object_expression) => object_expression.start(), Expr::MemberExpression(member_expression) => member_expression.start(), Expr::UnaryExpression(unary_expression) => unary_expression.start(), @@ -638,6 +643,7 @@ impl Expr { Expr::PipeExpression(pipe_expression) => pipe_expression.end(), Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.end(), Expr::ArrayExpression(array_expression) => array_expression.end(), + Expr::ArrayRangeExpression(array_range) => array_range.end(), Expr::ObjectExpression(object_expression) => object_expression.end(), Expr::MemberExpression(member_expression) => member_expression.end(), Expr::UnaryExpression(unary_expression) => unary_expression.end(), @@ -657,6 +663,7 @@ impl Expr { Expr::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code), Expr::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code), Expr::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code), + Expr::ArrayRangeExpression(array_range) => array_range.get_hover_value_for_position(pos, code), Expr::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code), Expr::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code), Expr::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code), @@ -685,6 +692,7 @@ impl Expr { Expr::PipeExpression(ref mut pipe_expression) => pipe_expression.rename_identifiers(old_name, new_name), Expr::PipeSubstitution(_) => {} Expr::ArrayExpression(ref mut array_expression) => array_expression.rename_identifiers(old_name, new_name), + Expr::ArrayRangeExpression(ref mut array_range) => array_range.rename_identifiers(old_name, new_name), Expr::ObjectExpression(ref mut object_expression) => { object_expression.rename_identifiers(old_name, new_name) } @@ -712,6 +720,7 @@ impl Expr { source_ranges: vec![pipe_substitution.into()], }, Expr::ArrayExpression(array_expression) => array_expression.get_constraint_level(), + Expr::ArrayRangeExpression(array_range) => array_range.get_constraint_level(), Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(), Expr::MemberExpression(member_expression) => member_expression.get_constraint_level(), Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(), @@ -2182,6 +2191,114 @@ impl ArrayExpression { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)] +#[databake(path = kcl_lib::ast::types)] +#[ts(export)] +#[serde(rename_all = "camelCase", tag = "type")] +pub struct ArrayRangeExpression { + pub start: usize, + pub end: usize, + pub start_element: Box, + pub end_element: Box, + /// Is the `end_element` included in the range? + pub end_inclusive: bool, + // TODO (maybe) comments on range components? + pub digest: Option, +} + +impl_value_meta!(ArrayRangeExpression); + +impl From for Expr { + fn from(array_expression: ArrayRangeExpression) -> Self { + Expr::ArrayRangeExpression(Box::new(array_expression)) + } +} + +impl ArrayRangeExpression { + pub fn new(start_element: Box, end_element: Box) -> Self { + Self { + start: 0, + end: 0, + start_element, + end_element, + end_inclusive: true, + digest: None, + } + } + + compute_digest!(|slf, hasher| { + hasher.update(slf.start_element.compute_digest()); + hasher.update(slf.end_element.compute_digest()); + }); + + pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) { + self.start_element.replace_value(source_range, new_value.clone()); + self.end_element.replace_value(source_range, new_value.clone()); + } + + pub fn get_constraint_level(&self) -> ConstraintLevel { + let mut constraint_levels = ConstraintLevels::new(); + constraint_levels.push(self.start_element.get_constraint_level()); + constraint_levels.push(self.end_element.get_constraint_level()); + + constraint_levels.get_constraint_level(self.into()) + } + + /// Returns a hover value that includes the given character position. + pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option { + for element in [&*self.start_element, &*self.end_element] { + let element_source_range: SourceRange = element.into(); + if element_source_range.contains(pos) { + return element.get_hover_value_for_position(pos, code); + } + } + + None + } + + #[async_recursion::async_recursion] + pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { + let metadata = Metadata::from(&*self.start_element); + let start = ctx + .execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression) + .await? + .get_json_value()?; + let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?; + let metadata = Metadata::from(&*self.end_element); + let end = ctx + .execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression) + .await? + .get_json_value()?; + let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?; + + if end < start { + return Err(KclError::Semantic(KclErrorDetails { + source_ranges: vec![self.into()], + message: format!("Range start is greater than range end: {start} .. {end}"), + })); + } + + let range: Vec<_> = if self.end_inclusive { + (start..=end).map(JValue::from).collect() + } else { + (start..end).map(JValue::from).collect() + }; + + Ok(KclValue::UserVal(UserVal { + value: range.into(), + meta: vec![Metadata { + source_range: self.into(), + }], + })) + } + + /// Rename all identifiers that have the old name to the new given name. + fn rename_identifiers(&mut self, old_name: &str, new_name: &str) { + self.start_element.rename_identifiers(old_name, new_name); + self.end_element.rename_identifiers(old_name, new_name); + } +} + #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)] #[databake(path = kcl_lib::ast::types)] #[ts(export)] @@ -2817,6 +2934,22 @@ pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange } } +pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result { + if let serde_json::Value::Number(n) = &j { + n.as_u64().ok_or_else(|| { + KclError::Syntax(KclErrorDetails { + source_ranges: vec![source_range], + message: format!("Invalid integer: {}", j), + }) + }) + } else { + Err(KclError::Syntax(KclErrorDetails { + source_ranges: vec![source_range], + message: format!("Invalid integer: {}", j), + })) + } +} + pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option { if let serde_json::Value::String(n) = &j { Some(n.clone()) @@ -3208,6 +3341,7 @@ async fn inner_execute_pipe_body( | Expr::PipeExpression(_) | Expr::PipeSubstitution(_) | Expr::ArrayExpression(_) + | Expr::ArrayRangeExpression(_) | Expr::ObjectExpression(_) | Expr::MemberExpression(_) | Expr::UnaryExpression(_) diff --git a/src/wasm-lib/kcl/src/docs/gen_std_tests.rs b/src/wasm-lib/kcl/src/docs/gen_std_tests.rs index 6f0a7718a..34c015076 100644 --- a/src/wasm-lib/kcl/src/docs/gen_std_tests.rs +++ b/src/wasm-lib/kcl/src/docs/gen_std_tests.rs @@ -784,6 +784,9 @@ fn test_generate_stdlib_markdown_docs() { #[test] fn test_generate_stdlib_json_schema() { + // If this test fails and you've modified the AST or something else which affects the json repr + // of stdlib functions, you should rerun the test with `EXPECTORATE=overwrite` to create new + // test data, then check `/docs/kcl/std.json` to ensure the changes are expected. let stdlib = StdLib::new(); let combined = stdlib.combined(); diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 01ea2e853..fe294030f 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -2139,6 +2139,7 @@ impl ExecutorContext { }, }, Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?, + Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?, Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?, Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?, Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?, diff --git a/src/wasm-lib/kcl/src/parser/parser_impl.rs b/src/wasm-lib/kcl/src/parser/parser_impl.rs index 09c0d6f75..fdb66a4ae 100644 --- a/src/wasm-lib/kcl/src/parser/parser_impl.rs +++ b/src/wasm-lib/kcl/src/parser/parser_impl.rs @@ -10,12 +10,12 @@ use winnow::{ use crate::{ ast::types::{ - ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, ElseIf, - Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, Literal, - LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, - ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, - TagDeclarator, UnaryExpression, UnaryOperator, ValueMeta, VariableDeclaration, VariableDeclarator, - VariableKind, + ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, + CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, + IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, + NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, + Program, ReturnStatement, TagDeclarator, UnaryExpression, UnaryOperator, ValueMeta, VariableDeclaration, + VariableDeclarator, VariableKind, }, errors::{KclError, KclErrorDetails}, executor::SourceRange, @@ -336,6 +336,7 @@ fn operand(i: TokenSlice) -> PResult { | Expr::PipeExpression(_) | Expr::PipeSubstitution(_) | Expr::ArrayExpression(_) + | Expr::ArrayRangeExpression(_) | Expr::ObjectExpression(_) => { return Err(KclError::Syntax(KclErrorDetails { source_ranges, @@ -466,8 +467,13 @@ pub enum NonCodeOr { } /// Parse a KCL array of elements. -fn array(i: TokenSlice) -> PResult { - alt((array_empty, array_elem_by_elem, array_end_start)).parse_next(i) +fn array(i: TokenSlice) -> PResult { + alt(( + array_empty.map(Box::new).map(Expr::ArrayExpression), + array_elem_by_elem.map(Box::new).map(Expr::ArrayExpression), + array_end_start.map(Box::new).map(Expr::ArrayRangeExpression), + )) + .parse_next(i) } /// Match an empty array. @@ -539,42 +545,26 @@ pub(crate) fn array_elem_by_elem(i: TokenSlice) -> PResult { }) } -fn array_end_start(i: TokenSlice) -> PResult { +fn array_end_start(i: TokenSlice) -> PResult { let start = open_bracket(i)?.start; ignore_whitespace(i); - let elements = integer_range - .context(expected("array contents, a numeric range (like 0..10)")) - .parse_next(i)?; + let start_element = Box::new(expression.parse_next(i)?); + ignore_whitespace(i); + double_period.parse_next(i)?; + ignore_whitespace(i); + let end_element = Box::new(expression.parse_next(i)?); ignore_whitespace(i); let end = close_bracket(i)?.end; - Ok(ArrayExpression { + Ok(ArrayRangeExpression { start, end, - elements, - non_code_meta: Default::default(), + start_element, + end_element, + end_inclusive: true, digest: None, }) } -/// Parse n..m into a vec of numbers [n, n+1, ..., m-1] -fn integer_range(i: TokenSlice) -> PResult> { - let (token0, floor) = integer.parse_next(i)?; - double_period.parse_next(i)?; - let (_token1, ceiling) = integer.parse_next(i)?; - Ok((floor..=ceiling) - .map(|num| { - let num = num as i64; - Expr::Literal(Box::new(Literal { - start: token0.start, - end: token0.end, - value: num.into(), - raw: num.to_string(), - digest: None, - })) - }) - .collect()) -} - fn object_property(i: TokenSlice) -> PResult { let key = identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?; colon @@ -1195,7 +1185,7 @@ fn expr_allowed_in_pipe_expr(i: TokenSlice) -> PResult { literal.map(Box::new).map(Expr::Literal), fn_call.map(Box::new).map(Expr::CallExpression), identifier.map(Box::new).map(Expr::Identifier), - array.map(Box::new).map(Expr::ArrayExpression), + array, object.map(Box::new).map(Expr::ObjectExpression), pipe_sub.map(Box::new).map(Expr::PipeSubstitution), function_expression.map(Box::new).map(Expr::FunctionExpression), @@ -1511,25 +1501,6 @@ fn expression_stmt(i: TokenSlice) -> PResult { }) } -/// Parse a KCL integer, and the token that held it. -fn integer(i: TokenSlice) -> PResult<(Token, u64)> { - let num = one_of(TokenType::Number) - .context(expected("a number token e.g. 3")) - .try_map(|token: Token| { - let source_ranges = token.as_source_ranges(); - let value = token.value.clone(); - token.value.parse().map(|num| (token, num)).map_err(|e| { - KclError::Syntax(KclErrorDetails { - source_ranges, - message: format!("invalid integer {value}: {e}"), - }) - }) - }) - .context(expected("an integer e.g. 3 (but not 3.1)")) - .parse_next(i)?; - Ok(num) -} - /// Parse the given brace symbol. fn some_brace(symbol: &'static str, i: TokenSlice) -> PResult { one_of((TokenType::Brace, symbol)) @@ -3060,123 +3031,6 @@ e } } - #[test] - fn test_parse_expand_array() { - let code = "const myArray = [0..10]"; - let parser = crate::parser::Parser::new(crate::token::lexer(code).unwrap()); - let result = parser.ast().unwrap(); - let expected_result = Program { - start: 0, - end: 23, - body: vec![BodyItem::VariableDeclaration(VariableDeclaration { - start: 0, - end: 23, - declarations: vec![VariableDeclarator { - start: 6, - end: 23, - id: Identifier { - start: 6, - end: 13, - name: "myArray".to_string(), - digest: None, - }, - init: Expr::ArrayExpression(Box::new(ArrayExpression { - start: 16, - end: 23, - non_code_meta: Default::default(), - elements: vec![ - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 0u32.into(), - raw: "0".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 1u32.into(), - raw: "1".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 2u32.into(), - raw: "2".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 3u32.into(), - raw: "3".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 4u32.into(), - raw: "4".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 5u32.into(), - raw: "5".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 6u32.into(), - raw: "6".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 7u32.into(), - raw: "7".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 8u32.into(), - raw: "8".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 9u32.into(), - raw: "9".to_string(), - digest: None, - })), - Expr::Literal(Box::new(Literal { - start: 17, - end: 18, - value: 10u32.into(), - raw: "10".to_string(), - digest: None, - })), - ], - digest: None, - })), - digest: None, - }], - kind: VariableKind::Const, - digest: None, - })], - non_code_meta: NonCodeMeta::default(), - digest: None, - }; - - assert_eq!(result, expected_result); - } - #[test] fn test_error_keyword_in_variable() { let some_program_string = r#"const let = "thing""#; diff --git a/src/wasm-lib/kcl/src/parser/snapshots/kcl_lib__parser__parser_impl__snapshot_tests__ac.snap b/src/wasm-lib/kcl/src/parser/snapshots/kcl_lib__parser__parser_impl__snapshot_tests__ac.snap index cbadc515f..584773e2a 100644 --- a/src/wasm-lib/kcl/src/parser/snapshots/kcl_lib__parser__parser_impl__snapshot_tests__ac.snap +++ b/src/wasm-lib/kcl/src/parser/snapshots/kcl_lib__parser__parser_impl__snapshot_tests__ac.snap @@ -24,111 +24,29 @@ expression: actual "digest": null }, "init": { - "type": "ArrayExpression", - "type": "ArrayExpression", + "type": "ArrayRangeExpression", + "type": "ArrayRangeExpression", "start": 16, "end": 23, - "elements": [ - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 0, - "raw": "0", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 1, - "raw": "1", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 2, - "raw": "2", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 3, - "raw": "3", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 4, - "raw": "4", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 5, - "raw": "5", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 6, - "raw": "6", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 7, - "raw": "7", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 8, - "raw": "8", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 9, - "raw": "9", - "digest": null - }, - { - "type": "Literal", - "type": "Literal", - "start": 17, - "end": 18, - "value": 10, - "raw": "10", - "digest": null - } - ], + "startElement": { + "type": "Literal", + "type": "Literal", + "start": 17, + "end": 18, + "value": 0, + "raw": "0", + "digest": null + }, + "endElement": { + "type": "Literal", + "type": "Literal", + "start": 20, + "end": 22, + "value": 10, + "raw": "10", + "digest": null + }, + "endInclusive": true, "digest": null }, "digest": null diff --git a/src/wasm-lib/kcl/src/unparser.rs b/src/wasm-lib/kcl/src/unparser.rs index d1f6ab779..9001f7dbe 100644 --- a/src/wasm-lib/kcl/src/unparser.rs +++ b/src/wasm-lib/kcl/src/unparser.rs @@ -2,10 +2,10 @@ use std::fmt::Write; use crate::{ ast::types::{ - ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, Expr, FormatOptions, - FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, - NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, - VariableKind, + ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, + Expr, FormatOptions, FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue, + MemberExpression, MemberObject, NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator, + UnaryExpression, VariableDeclaration, VariableKind, }, parser::PIPE_OPERATOR, }; @@ -113,6 +113,7 @@ impl Expr { match &self { Expr::BinaryExpression(bin_exp) => bin_exp.recast(options), Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, is_in_pipe), + Expr::ArrayRangeExpression(range_exp) => range_exp.recast(options, indentation_level, is_in_pipe), Expr::ObjectExpression(ref obj_exp) => obj_exp.recast(options, indentation_level, is_in_pipe), Expr::MemberExpression(mem_exp) => mem_exp.recast(), Expr::Literal(literal) => literal.recast(), @@ -280,6 +281,44 @@ impl ArrayExpression { } } +/// An expression is syntactically trivial: i.e., a literal, identifier, or similar. +fn expr_is_trivial(expr: &Expr) -> bool { + match expr { + Expr::Literal(_) | Expr::Identifier(_) | Expr::TagDeclarator(_) | Expr::PipeSubstitution(_) | Expr::None(_) => { + true + } + Expr::BinaryExpression(_) + | Expr::FunctionExpression(_) + | Expr::CallExpression(_) + | Expr::PipeExpression(_) + | Expr::ArrayExpression(_) + | Expr::ArrayRangeExpression(_) + | Expr::ObjectExpression(_) + | Expr::MemberExpression(_) + | Expr::UnaryExpression(_) + | Expr::IfExpression(_) => false, + } +} + +impl ArrayRangeExpression { + fn recast(&self, options: &FormatOptions, _: usize, _: bool) -> String { + let s1 = self.start_element.recast(options, 0, false); + let s2 = self.end_element.recast(options, 0, false); + + // Format these items into a one-line array. Put spaces around the `..` if either expression + // is non-trivial. This is a bit arbitrary but people seem to like simple ranges to be formatted + // tightly, but this is a misleading visual representation of the precedence if the range + // components are compound expressions. + if expr_is_trivial(&self.start_element) && expr_is_trivial(&self.end_element) { + format!("[{s1}..{s2}]") + } else { + format!("[{s1} .. {s2}]") + } + + // Assume a range expression fits on one line. + } +} + impl ObjectExpression { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String { if self @@ -830,6 +869,20 @@ myNestedVar = [callExp(bing.yo)] assert_eq!(recasted, some_program_string); } + #[test] + fn test_recast_ranges() { + let some_program_string = r#"foo = [0..10] +ten = 10 +bar = [0 + 1 .. ten] +"#; + let tokens = crate::token::lexer(some_program_string).unwrap(); + let parser = crate::parser::Parser::new(tokens); + let program = parser.ast().unwrap(); + + let recasted = program.recast(&Default::default(), 0); + assert_eq!(recasted, some_program_string); + } + #[test] fn test_recast_space_in_fn_call() { let some_program_string = r#"fn thing = (x) => { diff --git a/src/wasm-lib/kcl/src/walk/ast_node.rs b/src/wasm-lib/kcl/src/walk/ast_node.rs index f68efc728..6f7dbdfb2 100644 --- a/src/wasm-lib/kcl/src/walk/ast_node.rs +++ b/src/wasm-lib/kcl/src/walk/ast_node.rs @@ -24,6 +24,7 @@ pub enum Node<'a> { PipeExpression(&'a types::PipeExpression), PipeSubstitution(&'a types::PipeSubstitution), ArrayExpression(&'a types::ArrayExpression), + ArrayRangeExpression(&'a types::ArrayRangeExpression), ObjectExpression(&'a types::ObjectExpression), MemberExpression(&'a types::MemberExpression), UnaryExpression(&'a types::UnaryExpression), @@ -54,6 +55,7 @@ impl From<&Node<'_>> for SourceRange { Node::PipeExpression(p) => SourceRange([p.start(), p.end()]), Node::PipeSubstitution(p) => SourceRange([p.start(), p.end()]), Node::ArrayExpression(a) => SourceRange([a.start(), a.end()]), + Node::ArrayRangeExpression(a) => SourceRange([a.start(), a.end()]), Node::ObjectExpression(o) => SourceRange([o.start(), o.end()]), Node::MemberExpression(m) => SourceRange([m.start(), m.end()]), Node::UnaryExpression(u) => SourceRange([u.start(), u.end()]), @@ -90,6 +92,7 @@ impl_from!(Node, CallExpression); impl_from!(Node, PipeExpression); impl_from!(Node, PipeSubstitution); impl_from!(Node, ArrayExpression); +impl_from!(Node, ArrayRangeExpression); impl_from!(Node, ObjectExpression); impl_from!(Node, MemberExpression); impl_from!(Node, UnaryExpression); diff --git a/src/wasm-lib/kcl/src/walk/ast_walk.rs b/src/wasm-lib/kcl/src/walk/ast_walk.rs index 29f82016e..1a61931da 100644 --- a/src/wasm-lib/kcl/src/walk/ast_walk.rs +++ b/src/wasm-lib/kcl/src/walk/ast_walk.rs @@ -183,6 +183,18 @@ where } Ok(true) } + Expr::ArrayRangeExpression(are) => { + if !f.walk(are.as_ref().into())? { + return Ok(false); + } + if !walk_value::(&are.start_element, f)? { + return Ok(false); + } + if !walk_value::(&are.end_element, f)? { + return Ok(false); + } + Ok(true) + } Expr::ObjectExpression(oe) => walk_object_expression(oe, f), Expr::MemberExpression(me) => walk_member_expression(me, f), Expr::UnaryExpression(ue) => walk_unary_expression(ue, f), diff --git a/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_expr.kcl b/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_expr.kcl new file mode 100644 index 000000000..85f08b12c --- /dev/null +++ b/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_expr.kcl @@ -0,0 +1,17 @@ +r1 = [0..4] +assertEqual(r1[4], 4, 0.00001, "last element is included") + +four = 4 +zero = 0 +r2 = [zero..four] +assertEqual(r2[4], 4, 0.00001, "last element is included") + +five = int(four + 1) +r3 = [zero..five] +assertEqual(r3[4], 4, 0.00001, "second-to-last element is included") +assertEqual(r3[5], 5, 0.00001, "last element is included") + +r4 = [int(zero + 1) .. int(five - 1)] +assertEqual(r4[0], 1, 0.00001, "first element is 1") +assertEqual(r4[2], 3, 0.00001, "second-to-last element is 3") +assertEqual(r4[3], 4, 0.00001, "last element is 4") diff --git a/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_negative_expr.kcl b/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_negative_expr.kcl new file mode 100644 index 000000000..9d0584ffe --- /dev/null +++ b/src/wasm-lib/tests/executor/inputs/no_visuals/array_range_negative_expr.kcl @@ -0,0 +1,3 @@ +xs = [-5..5] +assertEqual(xs[0], -5, 0.001, "first element is -5") +assert(false) diff --git a/src/wasm-lib/tests/executor/no_visuals.rs b/src/wasm-lib/tests/executor/no_visuals.rs index c5e2cb0ab..f9fbbab8c 100644 --- a/src/wasm-lib/tests/executor/no_visuals.rs +++ b/src/wasm-lib/tests/executor/no_visuals.rs @@ -66,6 +66,8 @@ async fn run_fail(code: &str) -> KclError { gen_test!(property_of_object); gen_test!(index_of_array); gen_test!(comparisons); +gen_test!(array_range_expr); +gen_test_fail!(array_range_negative_expr, "syntax: Invalid integer: -5.0"); gen_test_fail!( invalid_index_str, "semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"