-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
413 additions
and
281 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { GraphModel, GraphNode } from '@cm2ml/ir' | ||
|
||
import type { PathData } from './paths' | ||
import type { Template } from './template' | ||
import { compileTemplate } from './template' | ||
|
||
export function encodePaths(paths: PathData[], model: GraphModel, templates: readonly string[]) { | ||
const nodes = [...model.nodes] | ||
const compiledTemplates = templates.map((template) => compileTemplate(template)) | ||
return paths.map((path) => encodePath(path, nodes, compiledTemplates)) | ||
} | ||
|
||
function encodePath(path: PathData, nodes: GraphNode[], templates: Template[]) { | ||
const parts: string[] = [] | ||
for (const step of path.steps) { | ||
const node = nodes[step] | ||
if (!node) { | ||
throw new Error(`Node index out-of-bounds. This is an internal error.`) | ||
} | ||
const mapper = templates.find((template) => template.isApplicable(node)) | ||
const mapped = mapper?.apply(node) | ||
if (mapped !== undefined) { | ||
parts.push(mapped) | ||
} | ||
} | ||
// TODO/Jan: Also encode the edges | ||
return parts.join(' -> ') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import type { ModelMember } from '@cm2ml/ir' | ||
|
||
const separator = '@' | ||
|
||
export const attributeKeyword = `attribute${separator}` | ||
|
||
class AttributeValueSelector { | ||
public constructor(public readonly attribute: string) { } | ||
|
||
public select(element: ModelMember) { | ||
return element.getAttribute(this.attribute)?.value.literal | ||
} | ||
|
||
public static fromString(selector: string) { | ||
if (!selector.startsWith(attributeKeyword)) { | ||
throw new Error(`Invalid selector: ${selector}`) | ||
} | ||
const attribute = selector.slice(attributeKeyword.length) | ||
if (attribute.includes(separator)) { | ||
throw new Error(`Invalid attribute: ${attribute}`) | ||
} | ||
return new AttributeValueSelector(attribute) | ||
} | ||
} | ||
|
||
const directAccessKeys = ['id', 'type', 'tag', 'name'] as const | ||
type DirectAccessKey = typeof directAccessKeys[number] | ||
function isDirectAccessKey(key: string): key is DirectAccessKey { | ||
return directAccessKeys.includes(key as DirectAccessKey) | ||
} | ||
|
||
class DirectSelector { | ||
public constructor(public readonly key: DirectAccessKey) { } | ||
|
||
public select(element: ModelMember) { | ||
return element[this.key] | ||
} | ||
|
||
public static fromString(selector: string) { | ||
if (!isDirectAccessKey(selector)) { | ||
return null | ||
} | ||
return new DirectSelector(selector) | ||
} | ||
} | ||
|
||
export function getSelector(selector: string) { | ||
return DirectSelector.fromString(selector) ?? AttributeValueSelector.fromString(selector) | ||
} | ||
|
||
export type Selector = ReturnType<typeof getSelector> | ||
|
||
class EqualityFilter { | ||
public constructor(public readonly selector: Selector, public readonly comparator: '=', public readonly target: string | Selector) { } | ||
|
||
public matches(element: ModelMember) { | ||
const selected = this.selector.select(element) | ||
const resolvedTarget = typeof this.target === 'string' ? this.target : this.target.select(element) | ||
return selected === resolvedTarget | ||
} | ||
|
||
public static fromString(filter: string) { | ||
const parts = filter.split('=') | ||
if (parts.length !== 2) { | ||
throw new Error(`Invalid filter: ${filter}`) | ||
} | ||
const selector = getSelector(parts[0]!) | ||
if (selector === null) { | ||
throw new Error(`Invalid filter: ${filter}`) | ||
} | ||
const target = parts[1]! | ||
if (target.at(0) === '"' && target.at(-1) === '"') { | ||
return new EqualityFilter(selector, '=', target.slice(1, -1)) | ||
} | ||
const targetSelector = getSelector(target) | ||
if (!targetSelector) { | ||
throw new Error(`Invalid filter: ${filter}`) | ||
} | ||
return new EqualityFilter(selector, '=', targetSelector) | ||
} | ||
} | ||
|
||
export function getFilter(filter: string) { | ||
return EqualityFilter.fromString(filter) | ||
} |
74 changes: 0 additions & 74 deletions
74
packages/encoder/bag-of-paths-encoder/src/node-encodings.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import type { ModelMember } from '@cm2ml/ir' | ||
import { Stream } from '@yeger/streams' | ||
|
||
import { getFilter, getSelector } from './mapper' | ||
|
||
export interface Template { | ||
isApplicable: (modelMember: ModelMember) => boolean | ||
apply: (modelMember: ModelMember) => string | ||
} | ||
|
||
export function compileTemplate(rawInput: string): Template { | ||
const { rawFilter, rawTemplate } = extractFilter(rawInput) | ||
if (!rawTemplate) { | ||
throw new Error(`Invalid template: ${rawInput}`) | ||
} | ||
const segments = getSegments(rawTemplate) | ||
if (!segments) { | ||
throw new Error(`Invalid template: ${rawInput}`) | ||
} | ||
const replacers = segments.map(parseSegment) | ||
const f = rawFilter ? getFilter(rawFilter) : undefined | ||
return { | ||
isApplicable: (modelMember: ModelMember) => !f || f.matches(modelMember), | ||
apply: (modelMember: ModelMember) => Stream.from(replacers).map((replace) => replace(modelMember)).filterNonNull().join(''), | ||
} | ||
} | ||
|
||
function extractFilter(template: string) { | ||
const parts = template.split('->') | ||
if (parts.length === 0) { | ||
return {} | ||
} | ||
if (parts.length === 1) { | ||
return { rawTemplate: parts[0]! } | ||
} | ||
if (parts.length === 2) { | ||
return { rawFilter: parts[0]!, rawTemplate: parts[1]! } | ||
} | ||
throw new Error(`Invalid template: ${template}`) | ||
} | ||
|
||
export function getSegments(template: string) { | ||
const parts = template.split('}{') | ||
if (parts.length === 0) { | ||
return null | ||
} | ||
parts[0] = parts[0]!.slice(1) | ||
parts[parts.length - 1] = parts[parts.length - 1]!.slice(0, -1) | ||
return parts | ||
} | ||
|
||
export function parseSegment(segment: string) { | ||
if (segment.startsWith('{') && segment.endsWith('}')) { | ||
const mapper = getSelector(segment.slice(1, -1)) | ||
return (modelMember: ModelMember): string | undefined => { | ||
const mapped = mapper.select(modelMember) | ||
if (Array.isArray(mapped)) { | ||
return mapped.join('') | ||
} | ||
return mapped | ||
} | ||
} | ||
return () => segment | ||
} |
10 changes: 10 additions & 0 deletions
10
packages/encoder/bag-of-paths-encoder/test/template.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { describe, expect, it } from 'vitest' | ||
|
||
import { getSegments } from '../src/template' | ||
|
||
describe('mapper', () => { | ||
it('can parse a template', () => { | ||
const result = getSegments('{a}{b}') | ||
expect(result).toEqual(['a', 'b']) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.