diff --git a/csharp/Platform.Protocols.Lino.Tests/SingleLineParserTests.cs b/csharp/Platform.Protocols.Lino.Tests/SingleLineParserTests.cs index 015fc1f..f57b612 100644 --- a/csharp/Platform.Protocols.Lino.Tests/SingleLineParserTests.cs +++ b/csharp/Platform.Protocols.Lino.Tests/SingleLineParserTests.cs @@ -312,5 +312,51 @@ public static void TestValueLinkParserTest() Assert.Null(result[0].Id); Assert.Equal(3, result[0].Values?.Count); } + + [Fact] + public static void TestRightArrowSyntaxTest() + { + var input = "(1 → 2)"; + var parser = new Parser(); + var links = parser.Parse(input); + var target = links.Format(); + Assert.Equal("(1 2)", target); + } + + [Fact] + public static void TestLeftArrowSyntaxTest() + { + var input = "(2 ← 1)"; + var parser = new Parser(); + var links = parser.Parse(input); + var target = links.Format(); + Assert.Equal("(1 2)", target); + } + + [Fact] + public static void TestArrowSyntaxEquivalenceTest() + { + var parser = new Parser(); + var normal = parser.Parse("(1 2)"); + var rightArrow = parser.Parse("(1 → 2)"); + var leftArrow = parser.Parse("(2 ← 1)"); + + var normalFormatted = normal.Format(); + var rightArrowFormatted = rightArrow.Format(); + var leftArrowFormatted = leftArrow.Format(); + + Assert.Equal(normalFormatted, rightArrowFormatted); + Assert.Equal(normalFormatted, leftArrowFormatted); + } + + [Fact] + public static void TestArrowSyntaxWithNamesTest() + { + var input = "(papa → mama)"; + var parser = new Parser(); + var links = parser.Parse(input); + var target = links.Format(); + Assert.Equal("(papa mama)", target); + } } } \ No newline at end of file diff --git a/csharp/Platform.Protocols.Lino/Parser.peg b/csharp/Platform.Protocols.Lino/Parser.peg index 6a79128..7ed08bc 100644 --- a/csharp/Platform.Protocols.Lino/Parser.peg +++ b/csharp/Platform.Protocols.Lino/Parser.peg @@ -11,9 +11,9 @@ anyLink > = ml:multiLineAnyLink eol { ml } / sl:singleLineAnyLink { multiLineAnyLink > = multiLineValueLink / multiLineLink singleLineAnyLink > = fl:singleLineLink eol { fl } / vl:singleLineValueLink eol { vl } multiLineValueAndWhitespace > = value:referenceOrLink _ { value } -multiLineValues >> = _ list:multiLineValueAndWhitespace* { list } +multiLineValues >> = _ list:arrowValues { list } / _ list:multiLineValueAndWhitespace* { list } singleLineValueAndWhitespace > = __ value:referenceOrLink { value } -singleLineValues >> = list:singleLineValueAndWhitespace+ { list } +singleLineValues >> = arrowValues / list:singleLineValueAndWhitespace+ { list } singleLineLink > = __ id:(reference) __ ":" v:singleLineValues { new Link(id, v) } multiLineLink > = "(" _ id:(reference) _ ":" v:multiLineValues _ ")" { new Link(id, v) } singleLineValueLink > = v:singleLineValues { new Link(v) } @@ -32,3 +32,7 @@ __ = [ \t]* _ = whiteSpaceSymbol* whiteSpaceSymbol = [ \t\n\r] referenceSymbol = [^ \t\n\r(:)] + +arrowValues >> = rightArrowValues / leftArrowValues +rightArrowValues >> = left:referenceOrLink _ "→" _ right:referenceOrLink { new List> { left, right } } +leftArrowValues >> = right:referenceOrLink _ "←" _ left:referenceOrLink { new List> { left, right } } diff --git a/csharp/Platform.Protocols.Lino/Platform.Protocols.Lino.csproj b/csharp/Platform.Protocols.Lino/Platform.Protocols.Lino.csproj index 3dd410d..aec75cb 100644 --- a/csharp/Platform.Protocols.Lino/Platform.Protocols.Lino.csproj +++ b/csharp/Platform.Protocols.Lino/Platform.Protocols.Lino.csproj @@ -4,7 +4,7 @@ LinksPlatform's Platform.Protocols.Lino Class Library Konstantin Diachenko Platform.Protocols.Lino - 0.6.0 + 0.7.0 Konstantin Diachenko net8 Platform.Protocols.Lino @@ -22,7 +22,9 @@ true snupkg latest - Now empty list of links is supported. + Added support for optional arrow syntax: (2 ← 1) = (1 → 2) = (1 2). +Arrow syntax provides intuitive directional notation for links. +Now empty list of links is supported. (:) and : syntax for empty links is now forbidden, to reduce confusion. Singlet links are supported, no more point link terminology. enable diff --git a/examples/arrow_syntax_test.js b/examples/arrow_syntax_test.js new file mode 100644 index 0000000..91ff12c --- /dev/null +++ b/examples/arrow_syntax_test.js @@ -0,0 +1,35 @@ +#!/usr/bin/env bun + +import { Parser } from '../js/src/Parser.js'; +import { formatLinks } from '../js/src/Link.js'; + +const parser = new Parser(); + +console.log("Testing Arrow Syntax Implementation"); +console.log("==================================="); + +// Test the examples from the issue +const tests = [ + "(2 ← 1)", + "(1 → 2)", + "(1 2)", + "(papa → mama)", + "(daughter ← papa)" +]; + +console.log("Testing equivalence: (2 ← 1) = (1 → 2) = (1 2)"); +console.log(""); + +for (const test of tests) { + try { + const result = parser.parse(test); + const formatted = formatLinks(result); + console.log(`Input: ${test}`); + console.log(`Output: ${formatted}`); + console.log(""); + } catch (error) { + console.log(`Error parsing "${test}": ${error.message}`); + } +} + +console.log("All tests completed!"); \ No newline at end of file diff --git a/js/package.json b/js/package.json index 99494d7..6704ed8 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "@linksplatform/protocols-lino", - "version": "0.6.0", + "version": "0.7.0", "description": "Lino protocol parser for JavaScript", "main": "dist/index.js", "type": "module", diff --git a/js/src/grammar.pegjs b/js/src/grammar.pegjs index b313eeb..219b29d 100644 --- a/js/src/grammar.pegjs +++ b/js/src/grammar.pegjs @@ -45,11 +45,11 @@ singleLineAnyLink = fl:singleLineLink eol { return fl; } multiLineValueAndWhitespace = value:referenceOrLink _ { return value; } -multiLineValues = _ list:multiLineValueAndWhitespace* { return list; } +multiLineValues = _ list:arrowValues { return list; } / _ list:multiLineValueAndWhitespace* { return list; } singleLineValueAndWhitespace = __ value:referenceOrLink { return value; } -singleLineValues = list:singleLineValueAndWhitespace+ { return list; } +singleLineValues = arrowValues / list:singleLineValueAndWhitespace+ { return list; } singleLineLink = __ id:reference __ ":" v:singleLineValues { return { id: id, values: v }; } @@ -81,4 +81,10 @@ _ = whiteSpaceSymbol* whiteSpaceSymbol = [ \t\n\r] -referenceSymbol = [^ \t\n\r(:)] \ No newline at end of file +referenceSymbol = [^ \t\n\r(:)] + +arrowValues = rightArrowValues / leftArrowValues + +rightArrowValues = left:referenceOrLink _ "→" _ right:referenceOrLink { return [left, right]; } + +leftArrowValues = right:referenceOrLink _ "←" _ left:referenceOrLink { return [left, right]; } \ No newline at end of file diff --git a/js/src/parser-generated.js b/js/src/parser-generated.js index 40ea078..fd28202 100644 --- a/js/src/parser-generated.js +++ b/js/src/parser-generated.js @@ -170,6 +170,8 @@ function peg$parse(input, options) { const peg$c3 = "\""; const peg$c4 = "'"; const peg$c5 = " "; + const peg$c6 = "\u2192"; + const peg$c7 = "\u2190"; const peg$r0 = /^[^"]/; const peg$r1 = /^[^']/; @@ -191,6 +193,8 @@ function peg$parse(input, options) { const peg$e10 = peg$classExpectation([" ", "\t"], false, false, false); const peg$e11 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false, false); const peg$e12 = peg$classExpectation([" ", "\t", "\n", "\r", "(", ":", ")"], true, false, false); + const peg$e13 = peg$literalExpectation("\u2192", false); + const peg$e14 = peg$literalExpectation("\u2190", false); function peg$f0(links) { return links; } function peg$f1() { return []; } @@ -209,18 +213,21 @@ function peg$parse(input, options) { function peg$f12(vl) { return vl; } function peg$f13(value) { return value; } function peg$f14(list) { return list; } - function peg$f15(value) { return value; } - function peg$f16(list) { return list; } - function peg$f17(id, v) { return { id: id, values: v }; } + function peg$f15(list) { return list; } + function peg$f16(value) { return value; } + function peg$f17(list) { return list; } function peg$f18(id, v) { return { id: id, values: v }; } - function peg$f19(v) { return { values: v }; } + function peg$f19(id, v) { return { id: id, values: v }; } function peg$f20(v) { return { values: v }; } - function peg$f21(chars) { return chars.join(''); } - function peg$f22(r) { return r.join(''); } + function peg$f21(v) { return { values: v }; } + function peg$f22(chars) { return chars.join(''); } function peg$f23(r) { return r.join(''); } - function peg$f24(spaces) { return spaces.length > getCurrentIndentation(); } - function peg$f25(spaces) { pushIndentation(spaces); } - function peg$f26(spaces) { return checkIndentation(spaces); } + function peg$f24(r) { return r.join(''); } + function peg$f25(spaces) { return spaces.length > getCurrentIndentation(); } + function peg$f26(spaces) { pushIndentation(spaces); } + function peg$f27(spaces) { return checkIndentation(spaces); } + function peg$f28(left, right) { return [left, right]; } + function peg$f29(right, left) { return [left, right]; } let peg$currPos = options.peg$currPos | 0; let peg$savedPos = peg$currPos; const peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -647,14 +654,26 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parse_(); - s2 = []; - s3 = peg$parsemultiLineValueAndWhitespace(); - while (s3 !== peg$FAILED) { - s2.push(s3); + s2 = peg$parsearrowValues(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = []; s3 = peg$parsemultiLineValueAndWhitespace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsemultiLineValueAndWhitespace(); + } + peg$savedPos = s0; + s0 = peg$f15(s2); } - peg$savedPos = s0; - s0 = peg$f14(s2); return s0; } @@ -667,7 +686,7 @@ function peg$parse(input, options) { s2 = peg$parsereferenceOrLink(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f15(s2); + s0 = peg$f16(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -679,22 +698,25 @@ function peg$parse(input, options) { function peg$parsesingleLineValues() { let s0, s1, s2; - s0 = peg$currPos; - s1 = []; - s2 = peg$parsesingleLineValueAndWhitespace(); - if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parsesingleLineValueAndWhitespace(); + s0 = peg$parsearrowValues(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = []; + s2 = peg$parsesingleLineValueAndWhitespace(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parsesingleLineValueAndWhitespace(); + } + } else { + s1 = peg$FAILED; } - } else { - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f16(s1); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f17(s1); + } + s0 = s1; } - s0 = s1; return s0; } @@ -718,7 +740,7 @@ function peg$parse(input, options) { s5 = peg$parsesingleLineValues(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f17(s2, s5); + s0 = peg$f18(s2, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -760,17 +782,22 @@ function peg$parse(input, options) { } if (s5 !== peg$FAILED) { s6 = peg$parsemultiLineValues(); - s7 = peg$parse_(); - if (input.charCodeAt(peg$currPos) === 41) { - s8 = peg$c2; - peg$currPos++; - } else { - s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e2); } - } - if (s8 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f18(s3, s6); + if (s6 !== peg$FAILED) { + s7 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 41) { + s8 = peg$c2; + peg$currPos++; + } else { + s8 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s8 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f19(s3, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { peg$currPos = s0; s0 = peg$FAILED; @@ -798,7 +825,7 @@ function peg$parse(input, options) { s1 = peg$parsesingleLineValues(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f19(s1); + s1 = peg$f20(s1); } s0 = s1; @@ -818,17 +845,22 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { s2 = peg$parsemultiLineValues(); - s3 = peg$parse_(); - if (input.charCodeAt(peg$currPos) === 41) { - s4 = peg$c2; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e2); } - } - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s0 = peg$f20(s2); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 41) { + s4 = peg$c2; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { peg$currPos = s0; s0 = peg$FAILED; @@ -871,7 +903,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f21(s1); + s1 = peg$f22(s1); } s0 = s1; @@ -922,7 +954,7 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f22(s2); + s0 = peg$f23(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -983,7 +1015,7 @@ function peg$parse(input, options) { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f23(s2); + s0 = peg$f24(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1023,7 +1055,7 @@ function peg$parse(input, options) { } } peg$savedPos = peg$currPos; - s2 = peg$f24(s1); + s2 = peg$f25(s1); if (s2) { s2 = undefined; } else { @@ -1031,7 +1063,7 @@ function peg$parse(input, options) { } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f25(s1); + s0 = peg$f26(s1); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1063,7 +1095,7 @@ function peg$parse(input, options) { } } peg$savedPos = peg$currPos; - s2 = peg$f26(s1); + s2 = peg$f27(s1); if (s2) { s2 = undefined; } else { @@ -1210,6 +1242,89 @@ function peg$parse(input, options) { return s0; } + function peg$parsearrowValues() { + let s0; + + s0 = peg$parserightArrowValues(); + if (s0 === peg$FAILED) { + s0 = peg$parseleftArrowValues(); + } + + return s0; + } + + function peg$parserightArrowValues() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsereferenceOrLink(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 8594) { + s3 = peg$c6; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + s5 = peg$parsereferenceOrLink(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f28(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseleftArrowValues() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsereferenceOrLink(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 8592) { + s3 = peg$c7; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + s5 = peg$parsereferenceOrLink(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f29(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + let indentationStack = [0]; diff --git a/js/tests/SingleLineParser.test.js b/js/tests/SingleLineParser.test.js index 70c1438..fbba4b2 100644 --- a/js/tests/SingleLineParser.test.js +++ b/js/tests/SingleLineParser.test.js @@ -226,4 +226,38 @@ test('Test value link (parser)', () => { expect(result.length).toBe(1); expect(result[0].id).toBe(null); expect(result[0].values.length).toBe(3); +}); + +test('Test right arrow syntax', () => { + const input = '(1 → 2)'; + const result = parser.parse(input); + const target = formatLinks(result); + expect(target).toBe('(1 2)'); +}); + +test('Test left arrow syntax', () => { + const input = '(2 ← 1)'; + const result = parser.parse(input); + const target = formatLinks(result); + expect(target).toBe('(1 2)'); +}); + +test('Test arrow syntax equivalence', () => { + const normal = parser.parse('(1 2)'); + const rightArrow = parser.parse('(1 → 2)'); + const leftArrow = parser.parse('(2 ← 1)'); + + const normalFormatted = formatLinks(normal); + const rightArrowFormatted = formatLinks(rightArrow); + const leftArrowFormatted = formatLinks(leftArrow); + + expect(rightArrowFormatted).toBe(normalFormatted); + expect(leftArrowFormatted).toBe(normalFormatted); +}); + +test('Test arrow syntax with names', () => { + const input = '(papa → mama)'; + const result = parser.parse(input); + const target = formatLinks(result); + expect(target).toBe('(papa mama)'); }); \ No newline at end of file diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6ccebfe..bf7484a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "platform-lino" -version = "0.6.0" +version = "0.7.0" edition = "2021" description = "Rust implementation of the Lino protocol parser" license = "Unlicense" diff --git a/rust/src/parser.rs b/rust/src/parser.rs index cffc71c..f10114a 100644 --- a/rust/src/parser.rs +++ b/rust/src/parser.rs @@ -91,6 +91,35 @@ fn is_reference_char(c: char) -> bool { !is_whitespace_char(c) && c != '(' && c != ':' && c != ')' } +fn arrow_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec> { + alt(( + |i| right_arrow_values(i, state), + |i| left_arrow_values(i, state) + )).parse(input) +} + +fn right_arrow_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec> { + ( + |i| reference_or_link(i, state), + whitespace, + char('→'), + whitespace, + |i| reference_or_link(i, state) + ).map(|(left, _, _, _, right)| vec![left, right]) + .parse(input) +} + +fn left_arrow_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec> { + ( + |i| reference_or_link(i, state), + whitespace, + char('←'), + whitespace, + |i| reference_or_link(i, state) + ).map(|(right, _, _, _, left)| vec![left, right]) // Note: order is swapped for left arrow + .parse(input) +} + fn horizontal_whitespace(input: &str) -> IResult<&str, &str> { take_while(is_horizontal_whitespace)(input) } @@ -159,7 +188,10 @@ fn multi_line_value_and_whitespace<'a>(input: &'a str, state: &ParserState) -> I fn multi_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec> { preceded( whitespace, - many0(|i| multi_line_value_and_whitespace(i, state)) + alt(( + |i| arrow_values(i, state), + many0(|i| multi_line_value_and_whitespace(i, state)) + )) ).parse(input) } @@ -171,7 +203,10 @@ fn single_line_value_and_whitespace<'a>(input: &'a str, state: &ParserState) -> } fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec> { - many1(|i| single_line_value_and_whitespace(i, state)).parse(input) + alt(( + |i| arrow_values(i, state), + many1(|i| single_line_value_and_whitespace(i, state)) + )).parse(input) } fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> { diff --git a/rust/tests/single_line_parser_tests.rs b/rust/tests/single_line_parser_tests.rs index fd44f35..3a51ca2 100644 --- a/rust/tests/single_line_parser_tests.rs +++ b/rust/tests/single_line_parser_tests.rs @@ -321,4 +321,42 @@ fn test_single_line_link() { assert_eq!(result.1.len(), 1); assert_eq!(result.1[0].id, Some("id".to_string())); assert_eq!(result.1[0].values.len(), 2); +} + +#[test] +fn test_right_arrow_syntax() { + let input = "(1 → 2)"; + let result = parse_lino(input).unwrap(); + let formatted = format_links_multiline(&result); + assert_eq!(formatted, "(1 2)"); +} + +#[test] +fn test_left_arrow_syntax() { + let input = "(2 ← 1)"; + let result = parse_lino(input).unwrap(); + let formatted = format_links_multiline(&result); + assert_eq!(formatted, "(1 2)"); +} + +#[test] +fn test_arrow_syntax_equivalence() { + let normal = parse_lino("(1 2)").unwrap(); + let right_arrow = parse_lino("(1 → 2)").unwrap(); + let left_arrow = parse_lino("(2 ← 1)").unwrap(); + + let normal_formatted = format_links_multiline(&normal); + let right_arrow_formatted = format_links_multiline(&right_arrow); + let left_arrow_formatted = format_links_multiline(&left_arrow); + + assert_eq!(normal_formatted, right_arrow_formatted); + assert_eq!(normal_formatted, left_arrow_formatted); +} + +#[test] +fn test_arrow_syntax_with_names() { + let input = "(papa → mama)"; + let result = parse_lino(input).unwrap(); + let formatted = format_links_multiline(&result); + assert_eq!(formatted, "(papa mama)"); } \ No newline at end of file