Index: ql/src/test/results/clientpositive/show_functions.q.out =================================================================== --- ql/src/test/results/clientpositive/show_functions.q.out (revision 963503) +++ ql/src/test/results/clientpositive/show_functions.q.out (working copy) @@ -107,6 +107,7 @@ rpad rtrim second +sentences sign sin size Index: ql/src/test/results/clientpositive/udf_sentences.q.out =================================================================== --- ql/src/test/results/clientpositive/udf_sentences.q.out (revision 0) +++ ql/src/test/results/clientpositive/udf_sentences.q.out (revision 0) @@ -0,0 +1,222 @@ +PREHOOK: query: CREATE TABLE sent_tmp (val array) +PREHOOK: type: CREATETABLE +POSTHOOK: query: CREATE TABLE sent_tmp (val array) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: default@sent_tmp +PREHOOK: query: CREATE TABLE sent_tmp2 (val string) +PREHOOK: type: CREATETABLE +POSTHOOK: query: CREATE TABLE sent_tmp2 (val string) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: default@sent_tmp2 +PREHOOK: query: INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("486976652065737420756E20657863656C6C656E74206F7574696C20706F7572206C65732072657175C3AA74657320646520646F6E6EC3A965732C20657420706575742DC3AA74726520706C757320706F6C7976616C656E7420717565206C612074726164756374696F6E206175746F6D61746971756521206C6120706F6E6374756174696F6E206D756C7469706C65732C206465732070687261736573206D616C20666F726DC3A96573202E2E2E20636F6E667573696F6E202D20657420706F757274616E742063652055444620666F6E6374696F6E6E6520656E636F72652121"), "fr")) AS val FROM src LIMIT 3 +PREHOOK: type: QUERY +PREHOOK: Input: default@src +PREHOOK: Output: default@sent_tmp +POSTHOOK: query: INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("486976652065737420756E20657863656C6C656E74206F7574696C20706F7572206C65732072657175C3AA74657320646520646F6E6EC3A965732C20657420706575742DC3AA74726520706C757320706F6C7976616C656E7420717565206C612074726164756374696F6E206175746F6D61746971756521206C6120706F6E6374756174696F6E206D756C7469706C65732C206465732070687261736573206D616C20666F726DC3A96573202E2E2E20636F6E667573696F6E202D20657420706F757274616E742063652055444620666F6E6374696F6E6E6520656E636F72652121"), "fr")) AS val FROM src LIMIT 3 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@src +POSTHOOK: Output: default@sent_tmp +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +PREHOOK: query: INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp +PREHOOK: type: QUERY +PREHOOK: Input: default@sent_tmp +PREHOOK: Output: default@sent_tmp2 +POSTHOOK: query: INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp +POSTHOOK: type: QUERY +POSTHOOK: Input: default@sent_tmp +POSTHOOK: Output: default@sent_tmp2 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: SELECT hex(val) FROM sent_tmp2 +PREHOOK: type: QUERY +PREHOOK: Input: default@sent_tmp2 +PREHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-19_526_2637797515524714813/10000 +POSTHOOK: query: SELECT hex(val) FROM sent_tmp2 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@sent_tmp2 +POSTHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-19_526_2637797515524714813/10000 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +48697665 +657374 +756E +657863656C6C656E74 +6F7574696C +706F7572 +6C6573 +72657175C3AA746573 +6465 +646F6E6EC3A96573 +6574 +706575742DC3AA747265 +706C7573 +706F6C7976616C656E74 +717565 +6C61 +74726164756374696F6E +6175746F6D617469717565 +6C61 +706F6E6374756174696F6E +6D756C7469706C6573 +646573 +70687261736573 +6D616C +666F726DC3A96573 +636F6E667573696F6E +6574 +706F757274616E74 +6365 +554446 +666F6E6374696F6E6E65 +656E636F7265 +48697665 +657374 +756E +657863656C6C656E74 +6F7574696C +706F7572 +6C6573 +72657175C3AA746573 +6465 +646F6E6EC3A96573 +6574 +706575742DC3AA747265 +706C7573 +706F6C7976616C656E74 +717565 +6C61 +74726164756374696F6E +6175746F6D617469717565 +PREHOOK: query: DROP TABLE sent_tmp +PREHOOK: type: DROPTABLE +POSTHOOK: query: DROP TABLE sent_tmp +POSTHOOK: type: DROPTABLE +POSTHOOK: Output: default@sent_tmp +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: DROP TABLE sent_tmp2 +PREHOOK: type: DROPTABLE +POSTHOOK: query: DROP TABLE sent_tmp2 +POSTHOOK: type: DROPTABLE +POSTHOOK: Output: default@sent_tmp2 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: CREATE TABLE sent_tmp (val array) +PREHOOK: type: CREATETABLE +POSTHOOK: query: CREATE TABLE sent_tmp (val array) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: default@sent_tmp +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: CREATE TABLE sent_tmp2 (val string) +PREHOOK: type: CREATETABLE +POSTHOOK: query: CREATE TABLE sent_tmp2 (val string) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: default@sent_tmp2 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("48697665206973742065696E2061757367657A656963686E65746573205765726B7A6575672066C3BC7220646965204162667261676520766F6E20446174656E2C20756E64207669656C6C6569636874207669656C736569746967657220616C7320646965206D61736368696E656C6C6520C39C6265727365747A756E6721204D756C7469706C652C207363686C6563687420676562696C646574656E2053C3A4747A65202E2E2E205665727765636873656C756E6720496E74657270756E6B74696F6E202D20756E6420646F636820697374206469657365205544462066756E6B74696F6E6965727420696D6D6572206E6F63682121"), "de")) AS val FROM src LIMIT 3 +PREHOOK: type: QUERY +PREHOOK: Input: default@src +PREHOOK: Output: default@sent_tmp +POSTHOOK: query: INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("48697665206973742065696E2061757367657A656963686E65746573205765726B7A6575672066C3BC7220646965204162667261676520766F6E20446174656E2C20756E64207669656C6C6569636874207669656C736569746967657220616C7320646965206D61736368696E656C6C6520C39C6265727365747A756E6721204D756C7469706C652C207363686C6563687420676562696C646574656E2053C3A4747A65202E2E2E205665727765636873656C756E6720496E74657270756E6B74696F6E202D20756E6420646F636820697374206469657365205544462066756E6B74696F6E6965727420696D6D6572206E6F63682121"), "de")) AS val FROM src LIMIT 3 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@src +POSTHOOK: Output: default@sent_tmp +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp +PREHOOK: type: QUERY +PREHOOK: Input: default@sent_tmp +PREHOOK: Output: default@sent_tmp2 +POSTHOOK: query: INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp +POSTHOOK: type: QUERY +POSTHOOK: Input: default@sent_tmp +POSTHOOK: Output: default@sent_tmp2 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: SELECT hex(val) FROM sent_tmp2 +PREHOOK: type: QUERY +PREHOOK: Input: default@sent_tmp2 +PREHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-31_739_6627979306575702865/10000 +POSTHOOK: query: SELECT hex(val) FROM sent_tmp2 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@sent_tmp2 +POSTHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-31_739_6627979306575702865/10000 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +48697665 +697374 +65696E +61757367657A656963686E65746573 +5765726B7A657567 +66C3BC72 +646965 +41626672616765 +766F6E +446174656E +756E64 +7669656C6C6569636874 +7669656C7365697469676572 +616C73 +646965 +6D61736368696E656C6C65 +C39C6265727365747A756E67 +4D756C7469706C65 +7363686C65636874 +676562696C646574656E +53C3A4747A65 +5665727765636873656C756E67 +496E74657270756E6B74696F6E +756E64 +646F6368 +697374 +6469657365 +554446 +66756E6B74696F6E69657274 +696D6D6572 +6E6F6368 +PREHOOK: query: DROP TABLE sent_tmp +PREHOOK: type: DROPTABLE +POSTHOOK: query: DROP TABLE sent_tmp +POSTHOOK: type: DROPTABLE +POSTHOOK: Output: default@sent_tmp +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: DROP TABLE sent_tmp2 +PREHOOK: type: DROPTABLE +POSTHOOK: query: DROP TABLE sent_tmp2 +POSTHOOK: type: DROPTABLE +POSTHOOK: Output: default@sent_tmp2 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +PREHOOK: query: SELECT sentences("Hive is an excellent tool for data querying; and perhaps more versatile than machine translation!! Multiple, ill-formed sentences...confounding punctuation--and yet this UDF still works!!!!") FROM src LIMIT 1 +PREHOOK: type: QUERY +PREHOOK: Input: default@src +PREHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-35_364_8686350823159923121/10000 +POSTHOOK: query: SELECT sentences("Hive is an excellent tool for data querying; and perhaps more versatile than machine translation!! Multiple, ill-formed sentences...confounding punctuation--and yet this UDF still works!!!!") FROM src LIMIT 1 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@src +POSTHOOK: Output: file:/var/folders/7i/7iCDbWRkGHOcgJgX0zscimPXXts/-Tmp-/mlahiri/hive_2010-07-12_14-58-35_364_8686350823159923121/10000 +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp.val SCRIPT [] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +POSTHOOK: Lineage: sent_tmp2.val SCRIPT [(sent_tmp)sent_tmp.FieldSchema(name:val, type:array, comment:null), ] +[["Hive","is","an","excellent","tool","for","data","querying","and","perhaps","more","versatile","than","machine","translation"],["Multiple","ill-formed","sentences","confounding","punctuation","and","yet","this","UDF","still","works"]] Index: ql/src/test/queries/clientpositive/udf_sentences.q =================================================================== --- ql/src/test/queries/clientpositive/udf_sentences.q (revision 0) +++ ql/src/test/queries/clientpositive/udf_sentences.q (revision 0) @@ -0,0 +1,22 @@ +CREATE TABLE sent_tmp (val array); +CREATE TABLE sent_tmp2 (val string); +INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("486976652065737420756E20657863656C6C656E74206F7574696C20706F7572206C65732072657175C3AA74657320646520646F6E6EC3A965732C20657420706575742DC3AA74726520706C757320706F6C7976616C656E7420717565206C612074726164756374696F6E206175746F6D61746971756521206C6120706F6E6374756174696F6E206D756C7469706C65732C206465732070687261736573206D616C20666F726DC3A96573202E2E2E20636F6E667573696F6E202D20657420706F757274616E742063652055444620666F6E6374696F6E6E6520656E636F72652121"), "fr")) AS val FROM src LIMIT 3; +INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp; +SELECT hex(val) FROM sent_tmp2; +DROP TABLE sent_tmp; +DROP TABLE sent_tmp2; + + +CREATE TABLE sent_tmp (val array); +CREATE TABLE sent_tmp2 (val string); +INSERT OVERWRITE TABLE sent_tmp +SELECT explode(sentences(unhex("48697665206973742065696E2061757367657A656963686E65746573205765726B7A6575672066C3BC7220646965204162667261676520766F6E20446174656E2C20756E64207669656C6C6569636874207669656C736569746967657220616C7320646965206D61736368696E656C6C6520C39C6265727365747A756E6721204D756C7469706C652C207363686C6563687420676562696C646574656E2053C3A4747A65202E2E2E205665727765636873656C756E6720496E74657270756E6B74696F6E202D20756E6420646F636820697374206469657365205544462066756E6B74696F6E6965727420696D6D6572206E6F63682121"), "de")) AS val FROM src LIMIT 3; +INSERT OVERWRITE TABLE sent_tmp2 +SELECT explode(val) AS val FROM sent_tmp; +SELECT hex(val) FROM sent_tmp2; +DROP TABLE sent_tmp; +DROP TABLE sent_tmp2; + +SELECT sentences("Hive is an excellent tool for data querying\; and perhaps more versatile than machine translation!! Multiple, ill-formed sentences...confounding punctuation--and yet this UDF still works!!!!") FROM src LIMIT 1; Index: ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java =================================================================== --- ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java (revision 963503) +++ ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java (working copy) @@ -164,6 +164,7 @@ import org.apache.hadoop.hive.ql.udf.generic.GenericUDFMap; import org.apache.hadoop.hive.ql.udf.generic.GenericUDFOPNotNull; import org.apache.hadoop.hive.ql.udf.generic.GenericUDFOPNull; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDFSentences; import org.apache.hadoop.hive.ql.udf.generic.GenericUDFSize; import org.apache.hadoop.hive.ql.udf.generic.GenericUDFSplit; import org.apache.hadoop.hive.ql.udf.generic.GenericUDFStruct; @@ -383,6 +384,7 @@ registerGenericUDF("elt", GenericUDFElt.class); registerGenericUDF("concat_ws", GenericUDFConcatWS.class); registerGenericUDF("array_contains", GenericUDFArrayContains.class); + registerGenericUDF("sentences", GenericUDFSentences.class); // Generic UDTF's registerGenericUDTF("explode", GenericUDTFExplode.class); Index: ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFSentences.java =================================================================== --- ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFSentences.java (revision 0) +++ ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFSentences.java (revision 0) @@ -0,0 +1,141 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hive.ql.udf.generic; + +import java.util.ArrayList; +import java.util.Locale; +import java.text.BreakIterator; + +import org.apache.hadoop.hive.ql.exec.Description; +import org.apache.hadoop.hive.ql.exec.UDFArgumentException; +import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; +import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory; +import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; +import org.apache.hadoop.io.Text; + +/** + * GenericUDFSentences: splits a natural language chunk of text into sentences and words. + * + */ +@Description(name = "sentences", value = "_FUNC_(str, lang, country) - Splits str" + + " into arrays of sentences, where each sentence is an array of words. The 'lang' and" + + "'country' arguments are optional, and if omitted, the default locale is used.", + extended = "Example:\n" + + " > SELECT _FUNC_('Hello there! I am a UDF.') FROM src LIMIT 1;\n" + + " [ [\"Hello\", \"there\"], [\"I\", \"am\", \"a\", \"UDF\"] ]\n" + + " > SELECT _FUNC_(review, language) FROM movies;\n" + + "Unnecessary punctuation, such as periods and commas in English, is automatically stripped." + + " If specified, 'lang' should be a two-letter ISO-639 language code (such as 'en'), and " + + "'country' should be a two-letter ISO-3166 code (such as 'us'). Not all country and " + + "language codes are fully supported, and if an unsupported code is specified, a default " + + "locale is used to process that string.") +public class GenericUDFSentences extends GenericUDF { + private ObjectInspectorConverters.Converter[] converters; + + @Override + public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { + if (arguments.length < 1 || arguments.length > 3) { + throw new UDFArgumentLengthException( + "The function sentences takes between 1 and 3 arguments."); + } + + converters = new ObjectInspectorConverters.Converter[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + converters[i] = ObjectInspectorConverters.getConverter(arguments[i], + PrimitiveObjectInspectorFactory.writableStringObjectInspector); + } + + return ObjectInspectorFactory.getStandardListObjectInspector( + ObjectInspectorFactory.getStandardListObjectInspector( + PrimitiveObjectInspectorFactory.writableStringObjectInspector)); + } + + @Override + public Object evaluate(DeferredObject[] arguments) throws HiveException { + assert (arguments.length >= 1 && arguments.length <= 3); + if (arguments[0].get() == null) { + return null; + } + + // if there is more than 1 argument specified, a different natural language + // locale is being specified + Locale locale = null; + if(arguments.length > 1 && arguments[1].get() != null) { + Text language = (Text) converters[1].convert(arguments[1].get()); + Text country = null; + if(arguments.length > 2 && arguments[2].get() != null) { + country = (Text) converters[2].convert(arguments[2].get()); + } + if(country != null) { + locale = new Locale(language.toString().toLowerCase(), country.toString().toUpperCase()); + } else { + locale = new Locale(language.toString().toLowerCase()); + } + } else { + locale = Locale.getDefault(); + } + + // get the input and prepare the output + Text chunk = (Text) converters[0].convert(arguments[0].get()); + String text = chunk.toString(); + ArrayList > result = new ArrayList >(); + + // Parse out sentences using Java's text-handling API + BreakIterator bi = BreakIterator.getSentenceInstance(locale); + bi.setText(text); + int idx = 0; + while(bi.next() != BreakIterator.DONE) { + String sentence = text.substring(idx, bi.current()); + idx = bi.current(); + result.add(new ArrayList()); + + // Parse out words in the sentence + BreakIterator wi = BreakIterator.getWordInstance(locale); + wi.setText(sentence); + int widx = 0; + ArrayList sent_array = result.get(result.size()-1); + while(wi.next() != BreakIterator.DONE) { + String word = sentence.substring(widx, wi.current()); + widx = wi.current(); + if(Character.isLetterOrDigit(word.charAt(0))) { + sent_array.add(new Text(word)); + } + } + } + + return result; + } + + @Override + public String getDisplayString(String[] children) { + assert (children.length >= 1 && children.length <= 3); + String display = "sentences(" + children[0]; + if(children.length > 1) { + display += ", " + children[1]; + if(children.length > 2) { + display += ", " + children[2]; + } + } + display += ")"; + return display; + } +}