Skip to content

Commit 005efda

Browse files
author
Your Name
committed
feat: add text control methods and support for chained commands
- Introduced `setText` method for various data structures (array, stack, matrix, linkedlist, graph, tree) to manage text positioning. - Enhanced parser to recognize and process `setText` commands, including chained commands for text attributes (fontSize, color, fontWeight, fontFamily, align). - Updated method signatures and documentation to reflect new text control capabilities. - Implemented logic to handle comments and maintain formatting in the DSL reconstruction process.
1 parent 2653556 commit 005efda

6 files changed

Lines changed: 592 additions & 51 deletions

File tree

src/compiler/compiler.mjs

Lines changed: 205 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -553,13 +553,29 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
553553
'set', 'set_multiple', 'set_matrix', 'set_matrix_multiple',
554554
'add', 'insert', 'remove', 'remove_at', 'remove_subtree',
555555
'add_child', 'set_child', 'add_matrix_row', 'add_matrix_column',
556-
'remove_matrix_row', 'remove_matrix_column', 'add_matrix_border', 'insert_matrix_row', 'insert_matrix_column'
556+
'remove_matrix_row', 'remove_matrix_column', 'add_matrix_border', 'insert_matrix_row', 'insert_matrix_column', 'set_text', 'set_chained'
557557
].includes(command.type)) {
558558
const targetObject = pages.length > 0 ? pages[pages.length - 1]?.find(comp => comp.name === command.name) : null;
559559
if (targetObject) {
560560
const methodName = getMethodNameFromCommand(command);
561-
if (methodName && !isMethodSupported(targetObject.type, methodName)) {
562-
causeCompileError(`Method not supported\n\nMethod: ${methodName}\nComponent type: ${targetObject.type}\nComponent: ${command.name}`, command);
561+
if (methodName) {
562+
// For chained commands, validate against text object methods instead
563+
if (command.type === 'set_chained') {
564+
if (!isMethodSupported('text', methodName)) {
565+
causeCompileError(`Method not supported on linked text object\n\nMethod: ${methodName}\nComponent type: text\nParent component: ${command.name}`, command);
566+
}
567+
} else if (!isMethodSupported(targetObject.type, methodName)) {
568+
causeCompileError(`Method not supported\n\nMethod: ${methodName}\nComponent type: ${targetObject.type}\nComponent: ${command.name}`, command);
569+
} else if (targetObject.type === 'text' && command.type === 'set_multiple') {
570+
// Special check for multi-line methods on single-line text objects
571+
const body = targetObject.body;
572+
const isMultiLineMethod = methodName && (methodName.endsWith('s') || methodName.includes('Multiple'));
573+
const hasArrayValue = Array.isArray(body.value);
574+
575+
if (isMultiLineMethod && !hasArrayValue) {
576+
causeCompileError(`Cannot use multi-line method on single-line text\n\nMethod: ${methodName}\nText type: Single-line\nSuggestion: Use ${methodName.replace(/s$/, '')} instead, or convert to multi-line text with setValue([...])`, command);
577+
}
578+
}
563579
}
564580
}
565581
}
@@ -740,8 +756,11 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
740756
break;
741757
}
742758
} else {
743-
// For non-node components, ensure index is a number
744-
if (typeof indexOrNodeName !== 'number') {
759+
// For non-node components, ensure index is a number or undefined (for text objects)
760+
if (targetObject.type === "text" && indexOrNodeName === undefined) {
761+
// Allow undefined index for text objects (means replace entire value)
762+
index = undefined;
763+
} else if (typeof indexOrNodeName !== 'number') {
745764
causeCompileError(`Invalid index type\n\nExpected number, got: ${typeof indexOrNodeName}\nIndex: ${indexOrNodeName}\nProperty: ${property}\nComponent: ${name}`, command);
746765
break;
747766
}
@@ -753,7 +772,12 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
753772
if (targetObject.type === "text") {
754773
if (property === "value") {
755774
// For text components, check if we're setting an array element or the whole value
756-
if (isValidIndex) {
775+
if (index === undefined) {
776+
// No index provided - replace entire value
777+
// If current value is array and new value is string, replace with string
778+
// If current value is string and new value is string, replace with string
779+
body[property] = newValue;
780+
} else if (isValidIndex) {
757781
// Setting a specific line in a multi-line text
758782
if (!Array.isArray(body[property])) {
759783
// Convert single string to array if needed
@@ -770,7 +794,10 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
770794
}
771795
} else {
772796
// For other text properties (fontSize, color, etc.)
773-
if (isValidIndex) {
797+
if (index === undefined) {
798+
// No index provided - replace entire property value
799+
body[property] = newValue;
800+
} else if (isValidIndex) {
774801
// Setting array-based property
775802
if (!Array.isArray(body[property])) {
776803
// Convert single value to array, preserving existing value
@@ -1812,6 +1839,175 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
18121839
if (!body2.nodes.includes(parent2)) body2.nodes.push(parent2);
18131840
break;
18141841

1842+
case "set_text": {
1843+
const name = command.name;
1844+
const text = command.args.index; // text value comes from index field
1845+
const position = command.args.value.value; // position comes from token's value field
1846+
const targetObject = pages[pages.length - 1].find(comp => comp.name === name);
1847+
1848+
if (!targetObject) {
1849+
causeCompileError(`Component not on page\n\nName: ${name}`, command);
1850+
break;
1851+
}
1852+
1853+
// setText is not allowed on text objects themselves
1854+
if (targetObject.type === "text") {
1855+
causeCompileError(`setText cannot be used on text objects\n\nUse setValue, setFontSize, etc. instead.\nComponent: ${name}`, command);
1856+
break;
1857+
}
1858+
1859+
// Find the placement text component for this position
1860+
const textComponentName = `${name}_${position}`;
1861+
let textComponent = pages[pages.length - 1].find(comp => comp.name === textComponentName);
1862+
1863+
if (text === null || text === undefined) {
1864+
// Remove the text object if it exists
1865+
if (textComponent) {
1866+
const index = pages[pages.length - 1].indexOf(textComponent);
1867+
if (index > -1) {
1868+
pages[pages.length - 1].splice(index, 1);
1869+
}
1870+
}
1871+
// Also remove from the component definition if it was set as a property
1872+
if (targetObject.body[position]) {
1873+
delete targetObject.body[position];
1874+
}
1875+
} else {
1876+
// Set or update the text
1877+
if (textComponent) {
1878+
// Update existing text component
1879+
if (Array.isArray(text)) {
1880+
// Convert array to single line string
1881+
textComponent.body.value = text.join(' ');
1882+
} else {
1883+
textComponent.body.value = text;
1884+
}
1885+
} else {
1886+
// Check if there's a reference to another text object in the definition
1887+
const referencedTextName = targetObject.body[position];
1888+
if (referencedTextName && typeof referencedTextName === 'string') {
1889+
// Find if this references an existing text component
1890+
const referencedComponent = findComponentDefinitionByName(definitions, referencedTextName);
1891+
if (referencedComponent && referencedComponent.type === 'text') {
1892+
// Update the referenced text object directly
1893+
if (Array.isArray(text)) {
1894+
referencedComponent.body.value = text.join(' ');
1895+
} else {
1896+
referencedComponent.body.value = text;
1897+
}
1898+
// Find the text component on the current page and update it
1899+
textComponent = pages[pages.length - 1].find(comp => comp.name === referencedTextName);
1900+
if (textComponent) {
1901+
if (Array.isArray(text)) {
1902+
textComponent.body.value = text.join(' ');
1903+
} else {
1904+
textComponent.body.value = text;
1905+
}
1906+
}
1907+
break;
1908+
}
1909+
}
1910+
1911+
// Create new text component
1912+
const newTextComponent = {
1913+
type: 'text',
1914+
name: textComponentName,
1915+
body: { value: Array.isArray(text) ? text.join(' ') : text },
1916+
position: 'previous',
1917+
placement: position
1918+
};
1919+
pages[pages.length - 1].push(newTextComponent);
1920+
1921+
// Also update the main component's definition to reference this text
1922+
targetObject.body[position] = textComponentName;
1923+
}
1924+
}
1925+
break;
1926+
}
1927+
1928+
case "set_chained": {
1929+
const name = command.name;
1930+
const placement = Array.isArray(command.placement) ? command.placement[0].value : command.placement;
1931+
const property = command.target;
1932+
const args = command.args;
1933+
const targetObject = pages[pages.length - 1].find(comp => comp.name === name);
1934+
1935+
if (!targetObject) {
1936+
causeCompileError(`Component not on page\n\nName: ${name}`, command);
1937+
break;
1938+
}
1939+
1940+
// Find the text component for this placement
1941+
const textComponentName = `${name}_${placement}`;
1942+
let textComponent = pages[pages.length - 1].find(comp => comp.name === textComponentName);
1943+
1944+
// Check if the placement refers to a referenced text object
1945+
if (!textComponent && targetObject.body[placement]) {
1946+
const referencedTextName = targetObject.body[placement];
1947+
if (typeof referencedTextName === 'string') {
1948+
textComponent = pages[pages.length - 1].find(comp => comp.name === referencedTextName);
1949+
}
1950+
}
1951+
1952+
if (!textComponent) {
1953+
// Create a new text component if it doesn't exist
1954+
textComponent = {
1955+
type: 'text',
1956+
name: textComponentName,
1957+
body: { value: "" },
1958+
position: 'previous',
1959+
placement: placement
1960+
};
1961+
pages[pages.length - 1].push(textComponent);
1962+
targetObject.body[placement] = textComponentName;
1963+
}
1964+
1965+
if (textComponent.type !== "text") {
1966+
causeCompileError(`Chained method can only be used on text objects\n\nComponent: ${textComponent.name} is not a text object`, command);
1967+
break;
1968+
}
1969+
1970+
// Apply the method to the text component using the same logic as regular text commands
1971+
if (property === "value") {
1972+
// Handle setValue for text - check if args has index or is direct value
1973+
if (typeof args === 'object' && args.index !== undefined) {
1974+
// Array-style operation with index: setValue(index, value)
1975+
if (!Array.isArray(textComponent.body[property])) {
1976+
textComponent.body[property] = [textComponent.body[property] || ""];
1977+
}
1978+
// Ensure array is long enough
1979+
while (textComponent.body[property].length <= args.index) {
1980+
textComponent.body[property].push("");
1981+
}
1982+
textComponent.body[property][args.index] = args.value;
1983+
} else {
1984+
// Direct value setting: setValue("text") or setValue(["line1", "line2"])
1985+
textComponent.body[property] = args;
1986+
}
1987+
} else {
1988+
// Handle other properties (fontSize, color, etc.)
1989+
if (typeof args === 'object' && args.index !== undefined) {
1990+
// Array-style operation with index
1991+
if (!Array.isArray(textComponent.body[property])) {
1992+
const existingValue = textComponent.body[property];
1993+
textComponent.body[property] = [];
1994+
if (existingValue !== undefined && existingValue !== null) {
1995+
textComponent.body[property][0] = existingValue;
1996+
}
1997+
}
1998+
// Ensure array is long enough
1999+
while (textComponent.body[property].length <= args.index) {
2000+
textComponent.body[property].push(null);
2001+
}
2002+
textComponent.body[property][args.index] = args.value;
2003+
} else {
2004+
// Direct property setting
2005+
textComponent.body[property] = args;
2006+
}
2007+
}
2008+
break;
2009+
}
2010+
18152011
}
18162012
});
18172013

@@ -1907,7 +2103,7 @@ function preCheck(parsedDSL) {
19072103
if (![
19082104
"page", "show", "hide", "set", "set_multiple", "set_matrix", "set_matrix_multiple",
19092105
"add", "insert", "remove", "remove_subtree", "remove_at", "comment",
1910-
"add_matrix_row", "add_matrix_column", "remove_matrix_row", "remove_matrix_column", "insert_matrix_row", "insert_matrix_column", "add_matrix_border", "add_child", "set_child"
2106+
"add_matrix_row", "add_matrix_column", "remove_matrix_row", "remove_matrix_column", "insert_matrix_row", "insert_matrix_column", "add_matrix_border", "add_child", "set_child", "set_text", "set_chained"
19112107
].includes(cmd.type)) {
19122108
throw createPreCheckError(
19132109
`Unknown command\n\nType: ${cmd.type}`,
@@ -1918,7 +2114,7 @@ function preCheck(parsedDSL) {
19182114

19192115

19202116
// Check if the first command is a page (only if there are commands)
1921-
if (parsedDSL.cmds.length > 0 && parsedDSL.cmds[0].type !== "page") {
2117+
if (parsedDSL.cmds.length > 0 && parsedDSL.cmds[0].type !== "page" && parsedDSL.cmds[0].type !== "comment") {
19222118
throw createPreCheckError("Command before page\n\nPlease start a page using 'page'.\nThen use any other commands.", parsedDSL.cmds[0]);
19232119
}
19242120
}

0 commit comments

Comments
 (0)