chore: can generator middle struct

This commit is contained in:
Joel 2025-01-06 15:31:18 +08:00
parent 07aa2ca9cf
commit e0ed17a2e6
3 changed files with 230 additions and 8 deletions

View File

@ -0,0 +1,93 @@
import { parseDSL } from './graph-to-log-struct-2'
describe('parseDSL', () => {
test('parse plain flow', () => {
const dsl = 'a -> b -> c'
const result = parseDSL(dsl)
expect(result).toEqual([
{ nodeType: 'plain', nodeId: 'a' },
{ nodeType: 'plain', nodeId: 'b' },
{ nodeType: 'plain', nodeId: 'c' },
])
})
test('parse iteration node with flow', () => {
const dsl = '(iteration, a, b -> c)'
const result = parseDSL(dsl)
expect(result).toEqual([
{
nodeType: 'iteration',
nodeId: 'a',
params: [
[
{ nodeType: 'plain', nodeId: 'b' },
{ nodeType: 'plain', nodeId: 'c' },
],
],
},
])
})
test('parse parallel node with flow', () => {
const dsl = 'a -> (parallel, b, c -> d, e)'
const result = parseDSL(dsl)
expect(result).toEqual([
{
nodeType: 'plain',
nodeId: 'a',
},
{
nodeType: 'parallel',
nodeId: 'b',
params: [
[
{ nodeType: 'plain', nodeId: 'c' },
{ nodeType: 'plain', nodeId: 'd' },
],
// single node don't need to be wrapped in an array
{ nodeType: 'plain', nodeId: 'e' },
],
},
])
})
test('parse retry', () => {
const dsl = '(retry, a, 3)'
const result = parseDSL(dsl)
expect(result).toEqual([
{
nodeType: 'retry',
nodeId: 'a',
params: [3],
},
])
})
test('parse nested complex nodes', () => {
const dsl = '(iteration, a, b -> (parallel, e, f -> g, h))'
const result = parseDSL(dsl)
expect(result).toEqual([
{
nodeType: 'iteration',
nodeId: 'a',
params: [
[
{ nodeType: 'plain', nodeId: 'b' },
{
nodeType: 'parallel',
nodeId: 'e',
params: [
[
{ nodeType: 'plain', nodeId: 'f' },
{ nodeType: 'plain', nodeId: 'g' },
],
// single node don't need to be wrapped in an array
{ nodeType: 'plain', nodeId: 'h' },
],
},
],
],
},
])
})
})

View File

@ -0,0 +1,115 @@
type NodePlain = { nodeType: 'plain'; nodeId: string }
type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | NodeComplex | Node[])[] }
type Node = NodePlain | NodeComplex
/**
* Parses a DSL string into an array of node objects.
* @param dsl - The input DSL string.
* @returns An array of parsed nodes.
*/
function parseDSL(dsl: string): Node[] {
return parseTopLevelFlow(dsl).map(parseNode)
}
/**
* Splits a top-level flow string by "->", respecting nested structures.
* @param dsl - The DSL string to split.
* @returns An array of top-level segments.
*/
function parseTopLevelFlow(dsl: string): string[] {
const segments: string[] = []
let buffer = ''
let nested = 0
for (let i = 0; i < dsl.length; i++) {
const char = dsl[i]
if (char === '(') nested++
if (char === ')') nested--
if (char === '-' && dsl[i + 1] === '>' && nested === 0) {
segments.push(buffer.trim())
buffer = ''
i++ // Skip the ">" character
}
else {
buffer += char
}
}
if (buffer.trim())
segments.push(buffer.trim())
return segments
}
/**
* Parses a single node string.
* If the node is complex (e.g., has parentheses), it extracts the node type, node ID, and parameters.
* @param nodeStr - The node string to parse.
* @returns A parsed node object.
*/
function parseNode(nodeStr: string): Node {
// Check if the node is a complex node
if (nodeStr.startsWith('(') && nodeStr.endsWith(')')) {
const innerContent = nodeStr.slice(1, -1).trim() // Remove outer parentheses
let nested = 0
let buffer = ''
const parts: string[] = []
// Split the inner content by commas, respecting nested parentheses
for (let i = 0; i < innerContent.length; i++) {
const char = innerContent[i]
if (char === '(') nested++
if (char === ')') nested--
if (char === ',' && nested === 0) {
parts.push(buffer.trim())
buffer = ''
}
else {
buffer += char
}
}
parts.push(buffer.trim())
// Extract nodeType, nodeId, and params
const [nodeType, nodeId, ...paramsRaw] = parts
const params = parseParams(paramsRaw)
return {
nodeType: nodeType.trim(),
nodeId: nodeId.trim(),
params,
}
}
// If it's not a complex node, treat it as a plain node
return { nodeType: 'plain', nodeId: nodeStr.trim() }
}
/**
* Parses parameters of a complex node.
* Supports nested flows and complex sub-nodes.
* @param paramParts - The parameters string split by commas.
* @returns An array of parsed parameters (plain nodes, nested nodes, or flows).
*/
function parseParams(paramParts: string[]): (Node | Node[])[] {
return paramParts.map((part) => {
if (part.includes('->')) {
// Parse as a flow and return an array of nodes
return parseTopLevelFlow(part).map(parseNode)
}
else if (part.startsWith('(')) {
// Parse as a nested complex node
return parseNode(part)
}
else if (!isNaN(Number(part.trim()))) {
// Parse as a numeric parameter
return Number(part.trim())
}
else {
// Parse as a plain node
return parseNode(part)
}
})
}
export { parseDSL }

View File

@ -2,6 +2,7 @@ const STEP_SPLIT = '->'
const toNodeData = (step: string, info: Record<string, any> = {}): any => {
const [nodeId, title] = step.split('@')
const data: Record<string, any> = {
id: nodeId,
node_id: nodeId,
@ -48,8 +49,10 @@ const toIterationNodeData = ({
}) => {
const res = [toNodeData(nodeId, { isIteration: true })]
// TODO: handle inner node structure
for (let i = 0; i < children.length; i++)
res.push(toNodeData(`${children[i]}`, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } }))
for (let i = 0; i < children.length; i++) {
const step = `${children[i]}`
res.push(toNodeData(step, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } }))
}
return res
}
@ -104,12 +107,21 @@ export function parseNodeString(input: string): NodeStructure {
for (let i = 0; i < parts.length; i++) {
const part = parts[i]
if (typeof part === 'string' && part.startsWith('('))
result.params.push(parseNodeString(part))
else if (i === 0)
result.node = part as string
else
result.params.push(part as string)
if (typeof part === 'string') {
if (part.startsWith('('))
result.params.push(parseNodeString(part))
if (part.startsWith('[')) {
const content = part.slice(1, -1)
result.params.push(parseNodeString(content))
}
}
else if (i === 0) {
result.node = part as unknown as string
}
else {
result.params.push(part as unknown as string)
}
}
return result
@ -130,6 +142,8 @@ const toNodes = (input: string): any[] => {
const { node, params } = parseNodeString(step)
switch (node) {
case 'iteration':
console.log(params)
break
res.push(...toIterationNodeData({
nodeId: params[0] as string,
children: JSON.parse(params[1] as string) as number[],