Skip to content

Commit

Permalink
Removed import cycle.
Browse files Browse the repository at this point in the history
  • Loading branch information
amyjko committed Jul 14, 2024
1 parent 15c4c59 commit 8d87523
Show file tree
Hide file tree
Showing 25 changed files with 1,315 additions and 99 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
}
}
],
"plugins": ["@typescript-eslint", "compat"],
"rules": {
"import/no-cycle": [2, { "maxDepth": "∞" }]
},
"plugins": ["@typescript-eslint", "compat", "import"],
"root": true,
"env": {
"browser": true,
Expand Down
1,199 changes: 1,181 additions & 18 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"chalk": "^5.3.0",
"eslint": "^8",
"eslint-plugin-compat": "^4",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-svelte": "^2",
"npm-watch": "^0.11",
"prettier": "3",
Expand Down
3 changes: 2 additions & 1 deletion src/basis/Basis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import UnparsableExpression from '@nodes/UnparsableExpression';
import Project from '../models/Project';
import Example from '../nodes/Example';
import { Basis } from './Basis';
import DefaultLocale, { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocale from '../locale/DefaultLocale';
import DefaultLocales from '@locale/DefaultLocales';
import Templates from '@concepts/Templates';

const basis = Basis.getLocalizedBasis(DefaultLocales);
Expand Down
2 changes: 2 additions & 0 deletions src/db/Database.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { writable, type Writable } from 'svelte/store';
import concretize from '@locale/concretize';
import { deleteDoc, doc, setDoc } from 'firebase/firestore';
import { firestore, auth } from '@db/firebase';
import {
Expand Down Expand Up @@ -66,6 +67,7 @@ export class Database {
this,
locales,
defaultLocale,
concretize,
this.Settings.settings.locales,
);
this.Projects = new ProjectsDatabase(this);
Expand Down
15 changes: 13 additions & 2 deletions src/db/LocalesDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import type Setting from './Setting';
import type LanguageCode from '../locale/LanguageCode';
import type { RegionCode } from '../locale/Regions';
import type Tutorial from '../tutorial/Tutorial';
import DefaultLocale, { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocale from '../locale/DefaultLocale';
import Locales from '../locale/Locales';
import { type Concretizer } from '@locale/concretize';
import DefaultLocales from '@locale/DefaultLocales';

/** A cache of locales loaded */
export default class LocalesDatabase {
/** The concretizer */
private readonly concretize: Concretizer;

/** The database these locales are stored in */
private readonly database: Database;

Expand Down Expand Up @@ -41,9 +46,11 @@ export default class LocalesDatabase {
database: Database,
locales: SupportedLocale[],
defaultLocale: LocaleText,
concretize: Concretizer,
setting: Setting<SupportedLocale[]>,
) {
this.database = database;
this.concretize = concretize;
this.defaultLocale = defaultLocale;

// Store the default locale
Expand Down Expand Up @@ -86,7 +93,11 @@ export default class LocalesDatabase {

syncLocales() {
// Update the locales stores if it's changed.
const newLocales = new Locales(this.computeLocales(), DefaultLocale);
const newLocales = new Locales(
this.concretize,
this.computeLocales(),
DefaultLocale,
);
if (!newLocales.isEqualTo(get(this.locales)))
this.locales.set(newLocales);
}
Expand Down
3 changes: 2 additions & 1 deletion src/edit/Autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import NumberLiteral from '../nodes/NumberLiteral';
import Append from './Append';
import Reference from '../nodes/Reference';
import type Revision from './Revision';
import DefaultLocale, { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocale from '../locale/DefaultLocale';
import DefaultLocales from '@locale/DefaultLocales';
import getPreferredSpaces from '@parser/getPreferredSpaces';

test.each([
Expand Down
2 changes: 1 addition & 1 deletion src/examples/examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DB, Locales } from '../db/Database';
import { readdirSync, readFileSync } from 'fs';
import path from 'path';
import { getExampleGalleries, parseSerializedProject } from './examples';
import { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocales from '../locale/DefaultLocales';
import type { SerializedProject } from '../models/ProjectSchemas';
import Evaluator from '@runtime/Evaluator';
import ExceptionValue from '@values/ExceptionValue';
Expand Down
3 changes: 0 additions & 3 deletions src/locale/DefaultLocale.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import en from '../locale/en-US.json';
import type LocaleText from './LocaleText';
import Locales from './Locales';

const DefaultLocale = en as unknown as LocaleText;

export const DefaultLocales = new Locales([DefaultLocale], DefaultLocale);

export default DefaultLocale;
7 changes: 7 additions & 0 deletions src/locale/DefaultLocales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import DefaultLocale from './DefaultLocale';
import Locales from './Locales';
import concretize from './concretize';

const DefaultLocales = new Locales(concretize, [DefaultLocale], DefaultLocale);

export { DefaultLocales as default };
59 changes: 15 additions & 44 deletions src/locale/Locales.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import Markup from '@nodes/Markup';
import type Names from '../nodes/Names';
import type LanguageCode from './LanguageCode';
import { getLanguageDirection } from './LanguageCode';
import { localeToString } from './Locale';
import type LocaleText from './LocaleText';
import {
withoutAnnotations,
isUnwritten,
MachineTranslated,
} from './LocaleText';
import { toMarkup } from '../parser/toMarkup';
import {} from './LocaleText';
import type NodeRef from './NodeRef';
import type ValueRef from './ValueRef';
import type ConceptRef from './ConceptRef';
import { isUnwritten, MachineTranslated } from './LocaleText';
import { getLanguageDirection } from './LanguageCode';
import { localeToString } from './Locale';
import type { Concretizer } from './concretize';
import type Markup from '@nodes/Markup';

export type TemplateInput =
| number
Expand All @@ -24,19 +19,23 @@ export type TemplateInput =
| ValueRef
| ConceptRef;

/** We maintain cache a mapping from template strings to compiled markup, since they are fixed structures.
* We just reuse them with different inputs.*/
const TemplateToMarkupCache: Map<string, Markup> = new Map();

/** Represents a sequence of preferred locales, and a set of utility functions for extracting information from them. */
export default class Locales {
/** The function that concretizes. We take this to avoid circular imports, since this class is deeply nested in the Node hierarchy. */
private readonly concretizer: Concretizer;

/** The list of preferred locales */
private readonly locales: LocaleText[];

/** The fallback locale when none of the preferred locales have suitable strings. */
private readonly fallback: LocaleText;

constructor(locales: LocaleText[], fallback: LocaleText) {
constructor(
concretizer: Concretizer,
locales: LocaleText[],
fallback: LocaleText,
) {
this.concretizer = concretizer;
this.locales = locales.slice();
this.fallback = fallback;
}
Expand Down Expand Up @@ -169,35 +168,7 @@ export default class Locales {
typeof textOrQuery === 'string'
? textOrQuery
: this.get(textOrQuery);
return (
this.concretizeOrUndefined(template, ...inputs) ??
// Create a representation of a template that couldn't be concretized.
Markup.words(
`${this.get((l) => l.ui.template.unparsable)}: ${template}`,
)
);
}

concretizeOrUndefined(
template: string,
...inputs: TemplateInput[]
): Markup | undefined {
// Not written? Return the TBD string.
if (template === '' || isUnwritten(template))
return Markup.words(this.get((l) => l.ui.template.unwritten));

// Remove annotations.
template = withoutAnnotations(template);

// See if we've cached this template.
let markup = TemplateToMarkupCache.get(template);
if (markup === undefined) {
[markup] = toMarkup(template);
TemplateToMarkupCache.set(template, markup);
}

// Now concretize the markup with the given inputs.
return markup.concretize(this, inputs);
return this.concretizer(this, template, ...inputs);
}

getTermByID(id: string) {
Expand Down
3 changes: 2 additions & 1 deletion src/locale/concretize.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from 'vitest';
import DefaultLocale, { DefaultLocales } from './DefaultLocale';
import DefaultLocale from './DefaultLocale';
import DefaultLocales from './DefaultLocales';
import type { TemplateInput } from './Locales';

test.each([
Expand Down
51 changes: 51 additions & 0 deletions src/locale/concretize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { TemplateInput } from './Locales';
import type Locales from './Locales';
import { isUnwritten, withoutAnnotations } from './LocaleText';
import { toMarkup } from '@parser/toMarkup';
import Markup from '@nodes/Markup';

/** We maintain cache a mapping from template strings to compiled markup, since they are fixed structures.
* We just reuse them with different inputs.*/
const TemplateToMarkupCache: Map<string, Markup> = new Map();

export type Concretizer = (
locales: Locales,
template: string,
...inputs: TemplateInput[]
) => Markup;

export function concretizeOrUndefined(
locales: Locales,
template: string,
...inputs: TemplateInput[]
): Markup | undefined {
// Not written? Return the TBD string.
if (template === '' || isUnwritten(template))
return Markup.words(locales.get((l) => l.ui.template.unwritten));

// Remove annotations.
template = withoutAnnotations(template);

// See if we've cached this template.
let markup = TemplateToMarkupCache.get(template);
if (markup === undefined) {
[markup] = toMarkup(template);
TemplateToMarkupCache.set(template, markup);
}

// Now concretize the markup with the given inputs.
return markup.concretize(locales, inputs);
}

export default function concretize(
locales: Locales,
template: string,
...inputs: TemplateInput[]
): Markup {
return (
concretizeOrUndefined(locales, template, ...inputs) ??
Markup.words(
`${locales.get((l) => l.ui.template.unparsable)}: ${template}`,
)
);
}
3 changes: 2 additions & 1 deletion src/models/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Name from '@nodes/Name';
import Doc from '@nodes/Doc';
import type Definition from '@nodes/Definition';
import Templates from '@concepts/Templates';
import concretize from '@locale/concretize';

/**
* How we store projects in memory, mirroring the data in the deserialized form.
Expand Down Expand Up @@ -120,7 +121,7 @@ export default class Project {

// Get a Basis for the requested locales.
this.basis = Basis.getLocalizedBasis(
new Locales(this.data.locales, DefaultLocale),
new Locales(concretize, this.data.locales, DefaultLocale),
);

// Initialize default shares
Expand Down
6 changes: 3 additions & 3 deletions src/nodes/Delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { testConflict } from '@conflicts/TestUtilities';
import Delete from './Delete';
import IncompatibleInput from '../conflicts/IncompatibleInput';
import evaluateCode from '../runtime/evaluate';
import { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocales from '../locale/DefaultLocales';

test.each([
[
Expand All @@ -22,12 +22,12 @@ test.each([
'Expect %s no conflicts, %s to have %s with %s',
(good, bad, node, conflict) => {
testConflict(good, bad, node, conflict);
}
},
);

test.each([['⎡a•# b•#⎦⎡1 2⎦⎡1 3⎦ ⎡- b = 3', '⎡ 1 2 ⎦']])(
'%s = %s',
(code: string, value: string) => {
expect(evaluateCode(code)?.toWordplay(DefaultLocales)).toBe(value);
}
},
);
5 changes: 4 additions & 1 deletion src/nodes/FormattedLiteral.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { test, expect } from 'vitest';
import evaluateCode from '../runtime/evaluate';
import type LocaleText from '../locale/LocaleText';
import { readFileSync } from 'fs';
import DefaultLocale, { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocale from '../locale/DefaultLocale';
import Locales from '../locale/Locales';
import DefaultLocales from '@locale/DefaultLocales';
import concretize from '@locale/concretize';

/** Load a few locales for testing. */
const en = DefaultLocale;
Expand All @@ -24,6 +26,7 @@ test.each([
['`hola`/es`hello`/en', 'hello', [en]],
])('%s -> %s', async (code, value, locales: LocaleText[]) => {
const loc = new Locales(
concretize,
locales.length === 0 ? [en] : locales,
DefaultLocale,
);
Expand Down
4 changes: 2 additions & 2 deletions src/nodes/Insert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import MissingCell from '@conflicts/MissingCell';
import InvalidRow from '@conflicts/InvalidRow';
import IncompatibleInput from '../conflicts/IncompatibleInput';
import evaluateCode from '../runtime/evaluate';
import { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocales from '../locale/DefaultLocales';

test.each([
[
Expand Down Expand Up @@ -37,7 +37,7 @@ test.each([
'Expect %s no conflicts, %s to have %s with %s',
(good, bad, node, conflict) => {
testConflict(good, bad, node, conflict);
}
},
);

test.each([
Expand Down
14 changes: 7 additions & 7 deletions src/nodes/Markup.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { list, node, type Grammar, type Replacement } from './Node';
import type Spaces from '../parser/Spaces';
import type Node from './Node';
import type { FormattedText } from '../output/Phrase';
import type { FontWeight } from '../basis/Fonts';
import type Locales from '../locale/Locales';
import type { TemplateInput } from '../locale/Locales';
import Paragraph from './Paragraph';
import Glyphs from '../lore/Glyphs';
import Purpose from '../concepts/Purpose';
import Content from './Content';
import { list, node, type Grammar, type Replacement } from './Node';
import type Spaces from '../parser/Spaces';
import { toMarkup } from '../parser/toMarkup';
import Token from './Token';
import Sym from './Sym';
import type Node from './Node';
import Words from './Words';
import type { FormattedText } from '../output/Phrase';
import type { FontWeight } from '../basis/Fonts';
import type Locales from '../locale/Locales';
import type { TemplateInput } from '../locale/Locales';

/**
* To refer to an input, use a $, followed by the number of the input desired,
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/PropertyBind.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from 'vitest';
import evaluateCode from '../runtime/evaluate';
import { DefaultLocales } from '../locale/DefaultLocale';
import DefaultLocales from '../locale/DefaultLocales';

test.each([
['•Test(n•#)\nb: Test(1).n: 2\nb.n', '2'],
Expand Down
Loading

0 comments on commit 8d87523

Please sign in to comment.