Skip to content

Commit

Permalink
Merge pull request #3011 from quantified-uncertainty/with-location
Browse files Browse the repository at this point in the history
  • Loading branch information
OAGr committed Jan 31, 2024
2 parents 26f2e7e + e1ee728 commit 2da1b43
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 54 deletions.
6 changes: 6 additions & 0 deletions .changeset/two-melons-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quri/squiggle-lang": patch
"@quri/squiggle-components": patch
---

Adds new @location tag and Tag.getLocation
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ export function useScrollToEditorPath(path: SqValuePath) {
return () => {
if (editor) {
const value = findNode(path)?.value();
const location = value?.context?.findLocation();
const taggedLocation = value?.tags.location();
const location = taggedLocation || value?.context?.findLocation();

if (location) {
editor?.scrollTo(location.start.offset, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ fn = {|e| e}
bar = [x, fn]
@startOpen
@location
s = 4 to 10
@startClosed
Expand Down
4 changes: 2 additions & 2 deletions packages/squiggle-lang/__tests__/SqProject/SqProject_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test("test exports", async () => {
const project = SqProject.create();
project.setSource("main", "x = 5; export y = 6; z = 7; export t = 8");
expect(await runFetchExports(project, "main")).toBe(
'{y: 6, with tags {exportData: {sourceId: "main", path: ["y"]}}, t: 8, with tags {exportData: {sourceId: "main", path: ["t"]}}}, with tags {name: "main", exportData: {sourceId: "main", path: []}}'
'{y: 6, with tags {exportData: {sourceId: "main", path: ["y"]}}, t: 8, with tags {exportData: {sourceId: "main", path: ["t"]}}}, with tags {exportData: {sourceId: "main", path: []}, name: "main"}'
);
});

Expand All @@ -53,7 +53,7 @@ test("test decorated exports", async () => {
`
);
expect(await runFetchExports(project, "main")).toBe(
'{x: 5, with tags {name: "X", exportData: {sourceId: "main", path: ["x"]}}, y: 6, with tags {name: "Y", doc: "whatever", exportData: {sourceId: "main", path: ["y"]}}}, with tags {name: "main", exportData: {sourceId: "main", path: []}}'
'{x: 5, with tags {name: "X", exportData: {sourceId: "main", path: ["x"]}}, y: 6, with tags {doc: "whatever", name: "Y", exportData: {sourceId: "main", path: ["y"]}}}, with tags {exportData: {sourceId: "main", path: []}, name: "main"}'
);
});

Expand Down
14 changes: 12 additions & 2 deletions packages/squiggle-lang/__tests__/library/tag_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { testEvalToBe } from "../helpers/reducerHelpers.js";

describe("Tags", () => {
testEvalToBe("123 -> Tag.name('')", "123");
testEvalToBe("123 -> Tag.name('')", '123, with tags {name: ""}');
describe("name", () => {
testEvalToBe("123 -> Tag.name('myNumber') -> Tag.getName", '"myNumber"');
});
Expand Down Expand Up @@ -30,6 +30,16 @@ describe("Tags", () => {
testEvalToBe("3 -> Tag.hide -> Tag.getHide", "true");
});

describe("location", () => {
testEvalToBe(
`@location
a = 3
Tag.getLocation(a)
`,
'{source: "main", start: {line: 2, column: 1, offset: 10}, end: {line: 2, column: 6, offset: 15}}'
);
});

describe("startOpenToggle", () => {
testEvalToBe("3 -> Tag.startOpen -> Tag.getStartOpenState", '"open"');
testEvalToBe("3 -> Tag.startClosed -> Tag.getStartOpenState", '"closed"');
Expand Down Expand Up @@ -71,7 +81,7 @@ x = 5
x
`,
'5, with tags {name: "five", doc: "This is five"}'
'5, with tags {doc: "This is five", name: "five"}'
);

testEvalToBe(
Expand Down
36 changes: 34 additions & 2 deletions packages/squiggle-lang/src/fr/tag.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { REArgumentError } from "../errors/messages.js";
import { REArgumentError, REOther } from "../errors/messages.js";
import { makeFnExample } from "../library/registry/core.js";
import { makeDefinition } from "../library/registry/fnDefinition.js";
import {
Expand Down Expand Up @@ -31,6 +31,7 @@ import { Lambda } from "../reducer/lambda.js";
import { getOrThrow } from "../utility/result.js";
import { Value } from "../value/index.js";
import { ValueTags, ValueTagsType } from "../value/valueTags.js";
import { location, toMap } from "../value/valueTagsUtils.js";
import { vBool, VBool } from "../value/VBool.js";
import { vString } from "../value/VString.js";

Expand Down Expand Up @@ -389,13 +390,44 @@ example2 = {|x| x + 1}`,
}),
],
}),
maker.make({
name: "location",
description: `Saves the location of a value. Note that this must be called at the point where the location is to be saved. If you use it in a helper function, it will save the location of the helper function, not the location where the helper function is called.`,
displaySection: "Tags",
definitions: [
makeDefinition(
[frWithTags(frAny({ genericName: "A" }))],
frWithTags(frAny({ genericName: "A" })),
([{ value, tags }], { frameStack }) => {
const location = frameStack.getTopFrame()?.location;
if (!location) {
throw new REOther("Location is missing in call stack");
}
return {
value,
tags: tags.merge({ location: location }),
};
},
{ isDecorator: true }
),
],
}),
maker.make({
name: "getLocation",
displaySection: "Tags",
definitions: [
makeDefinition([frWithTags(frAny())], frAny(), ([{ tags }]) => {
return location(tags) || vString("None");
}),
],
}),
maker.make({
name: "getAll",
displaySection: "Functions",
description: "Returns a dictionary of all tags on a value.",
definitions: [
makeDefinition([frAny()], frDictWithArbitraryKeys(frAny()), ([value]) => {
return value.getTags().toMap();
return toMap(value.getTags());
}),
],
}),
Expand Down
6 changes: 6 additions & 0 deletions packages/squiggle-lang/src/public/SqValue/SqTags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LocationRange } from "peggy";

import { ValueTags } from "../../value/valueTags.js";
import { SqValueContext } from "../SqValueContext.js";
import { SqValue, wrapValue } from "./index.js";
Expand Down Expand Up @@ -40,4 +42,8 @@ export class SqTags {
startOpenState(): "open" | "closed" | undefined {
return this.tags.startOpenState();
}

location(): LocationRange | undefined {
return this.tags.location();
}
}
73 changes: 26 additions & 47 deletions packages/squiggle-lang/src/value/valueTags.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ImmutableMap } from "../utility/immutableMap.js";
import { LocationRange } from "peggy";

import { Err, fmap, mergeMany, Ok, result } from "../utility/result.js";
import { Value } from "./index.js";
import { type VBool } from "./VBool.js";
import { type VDict } from "./VDict.js";
import { VDict } from "./VDict.js";
import { type VString } from "./VString.js";

// Note: this file can't call any `vType` constructors; it would cause a circular dependency because of `BaseValue` -> `ValueTags`.
Expand All @@ -18,6 +19,7 @@ export type ValueTagsType = {
notebook?: VBool; // can be set on arrays
exportData?: VDict; // should be { sourceId: String, path: List(String) }
startOpenState?: VString;
location?: LocationRange;
};

type ValueTagsTypeName = keyof ValueTagsType;
Expand All @@ -32,6 +34,7 @@ const valueTagsTypeNames: ValueTagsTypeName[] = [
"notebook",
"exportData",
"startOpenState",
"location",
];

function convertToValueTagsTypeName(
Expand All @@ -54,43 +57,25 @@ function convertToValueTagsTypeName(
export class ValueTags {
constructor(public value: ValueTagsType) {}

toList(): [string, Value][] {
const result: [string, Value][] = [];
const { value } = this;
if (value.name?.value) {
result.push(["name", value.name]);
}
if (value.doc?.value) {
result.push(["doc", value.doc]);
}
if (value.showAs) {
result.push(["showAs", value.showAs]);
}
if (value.numberFormat) {
result.push(["numberFormat", value.numberFormat]);
}
if (value.dateFormat) {
result.push(["dateFormat", value.dateFormat]);
}
if (value.hidden) {
result.push(["hidden", value.hidden]);
}
if (value.notebook) {
result.push(["notebook", value.notebook]);
}
const _exportData = this.exportData();
if (_exportData) {
result.push(["exportData", _exportData]);
}

if (value.startOpenState) {
result.push(["startOpenState", value.startOpenState]);
}
return result;
isEmpty() {
return (
this.value.name === undefined &&
this.value.doc === undefined &&
this.value.showAs === undefined &&
this.value.numberFormat === undefined &&
this.value.dateFormat === undefined &&
this.value.hidden === undefined &&
this.value.notebook === undefined &&
this.value.exportData === undefined &&
this.value.startOpenState === undefined &&
this.value.location === undefined
);
}

isEmpty() {
return this.toList().length === 0;
toString(): string {
return Object.entries(this.value)
.map(([key, value]) => `${key}: ${value?.toString()}`)
.join(", ");
}

omit(keys: ValueTagsTypeName[]) {
Expand All @@ -105,16 +90,6 @@ export class ValueTags {
return fmap(params, (args) => this.omit(args));
}

toMap(): ImmutableMap<string, Value> {
return ImmutableMap(this.toList());
}

toString(): string {
return this.toList()
.map(([key, value]) => `${key}: ${value.toString()}`)
.join(", ");
}

merge(other: ValueTagsType) {
return new ValueTags({
...this.value,
Expand Down Expand Up @@ -150,6 +125,10 @@ export class ValueTags {
return this.value.notebook?.value;
}

location() {
return this.value.location;
}

startOpenState(): "open" | "closed" | undefined {
const { value } = this.value.startOpenState ?? {};
if (!value) {
Expand Down
82 changes: 82 additions & 0 deletions packages/squiggle-lang/src/value/valueTagsUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Location, LocationRange } from "peggy";

import { ImmutableMap } from "../utility/immutableMap.js";
import { Value, vNumber, vString } from "./index.js";
import { ValueTags } from "./valueTags.js";
import { vDict, type VDict } from "./VDict.js";

// There are several value tags functions we can't add to ValueTags because Value is dependent on ValueTags, and that would create a circular dependency.

function locationRangeToValue(locationRange: LocationRange) {
function locationToDict(location: Location): VDict {
return vDict(
ImmutableMap([
["line", vNumber(location.line)],
["column", vNumber(location.column)],
["offset", vNumber(location.offset)],
])
);
}
let items: [string, Value][] = [
["start", locationToDict(locationRange.start)],
["end", locationToDict(locationRange.end)],
];
if (typeof locationRange.source === "string") {
items = [["source", vString(locationRange.source)], ...items];
}
return vDict(ImmutableMap(items));
}

export const toList = (tags: ValueTags): [string, Value][] => {
const result: [string, Value][] = [];
const { value } = tags;
if (value.name?.value !== undefined) {
result.push(["name", value.name]);
}
if (value.doc?.value !== undefined) {
result.push(["doc", value.doc]);
}
if (value.showAs !== undefined) {
result.push(["showAs", value.showAs]);
}
if (value.numberFormat !== undefined) {
result.push(["numberFormat", value.numberFormat]);
}
if (value.dateFormat !== undefined) {
result.push(["dateFormat", value.dateFormat]);
}
if (value.hidden !== undefined) {
result.push(["hidden", value.hidden]);
}
if (value.notebook !== undefined) {
result.push(["notebook", value.notebook]);
}
const _exportData = tags.exportData();
if (_exportData !== undefined) {
result.push(["exportData", _exportData]);
}

if (value.startOpenState !== undefined) {
result.push(["startOpenState", value.startOpenState]);
}
if (value.location !== undefined) {
result.push(["location", locationRangeToValue(value.location)]);
}

return result;
};

export const toMap = (tags: ValueTags): ImmutableMap<string, Value> => {
return ImmutableMap(toList(tags));
};

export const toString = (tags: ValueTags): string => {
return toList(tags)
.map(([key, value]) => `${key}: ${value.toString()}`)
.join(", ");
};

export const location = (tags: ValueTags): VDict | undefined => {
const _location = tags.location();
return _location ? locationRangeToValue(_location) : undefined;
};

0 comments on commit 2da1b43

Please sign in to comment.