From daceafeeb90680116c289a7c301c42eb3e57eb97 Mon Sep 17 00:00:00 2001 From: bjculkin Date: Tue, 28 Mar 2017 16:48:43 -0400 Subject: Mostly finish FDS core The major changes this time are that we have both data/command macros, as well as proper Unicode character support. --- .../src/main/java/bjc/utils/cli/fds/FDS.java | 161 ++++++++++++++++----- .../src/main/java/bjc/utils/cli/fds/FDSMode.java | 12 +- .../src/main/java/bjc/utils/cli/fds/FDSState.java | 54 +++++-- .../src/main/java/bjc/utils/cli/fds/FDSUtils.java | 4 +- .../main/java/bjc/utils/cli/fds/MacroFDSMode.java | 79 ++++++++++ .../main/java/bjc/utils/cli/fds/SimpleFDSMode.java | 73 +++++++--- .../main/java/bjc/utils/funcutils/StringUtils.java | 38 ++++- 7 files changed, 332 insertions(+), 89 deletions(-) create mode 100644 BJC-Utils2/src/main/java/bjc/utils/cli/fds/MacroFDSMode.java (limited to 'BJC-Utils2/src/main/java/bjc') diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDS.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDS.java index 59b673a..d94aae0 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDS.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDS.java @@ -5,14 +5,20 @@ import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import bjc.utils.cli.CommandHelp; import bjc.utils.cli.GenericHelp; import bjc.utils.cli.fds.FDSState.InputMode; +import bjc.utils.funcutils.StringUtils; import bjc.utils.ioutils.Block; import bjc.utils.ioutils.BlockReader; import bjc.utils.ioutils.PushbackBlockReader; +import com.ibm.icu.text.BreakIterator; + import static bjc.utils.ioutils.BlockReaders.*; /** @@ -30,24 +36,66 @@ public class FDS { private static SimpleFDSMode miscMode; static { - miscMode = new SimpleFDSMode<>(); + miscMode = new SimpleFDSMode<>("MSC"); + configureMiscMode(); + } + private static void configureMiscMode() { GenericHelp loadScriptHelp = new GenericHelp("load-script\tLoad a script from a file", ""); - miscMode.addCommand('X', FDS::loadScript, loadScriptHelp); + miscMode.addCommand("X", FDS::loadScript, loadScriptHelp); GenericHelp quitProgramHelp = new GenericHelp("quit-program\tQuit the program", ""); - miscMode.addCommand('Q', (state) -> state.modes.drop(state.modes.size()), quitProgramHelp); + miscMode.addCommand("Q", (state) -> state.modes.drop(state.modes.size()), quitProgramHelp); - GenericHelp inlineInputHelp = new GenericHelp("", ""); - miscMode.addCommand('I', (state) -> { - if (state.mode == InputMode.CHORD) { + GenericHelp inlineInputHelp = new GenericHelp("inline-input\tProvide data input inline with commands", + ""); + miscMode.addCommand("I", (state) -> { + if(state.mode == InputMode.CHORD) { state.mode = InputMode.INLINE; - } else if (state.mode == InputMode.INLINE) { + } else if(state.mode == InputMode.INLINE) { state.mode = InputMode.CHORD; } else { state.printer.printf("? MNV\n"); } }, inlineInputHelp); + + GenericHelp dataMacroHelp = new GenericHelp("input-macro\tDefine a macro for providing data", ""); + miscMode.addCommand("i", (state) -> createMacro(state, state.dataMacros), dataMacroHelp); + + GenericHelp comMacroHelp = new GenericHelp("command-macro\tDefine a macro for providing commands", ""); + miscMode.addCommand("c", (state) -> createMacro(state, state.commandMacros), comMacroHelp); + } + + private static void createMacro(FDSState state, Map> macroStore) { + PushbackBlockReader source = state.datain; + + String macroName; + List macroBody = new LinkedList<>(); + + state.dataPrompter.accept("Enter macro name: "); + Block blk = source.next(); + + String blkContents = blk.contents.trim(); + + BreakIterator charBreaker = BreakIterator.getCharacterInstance(); + charBreaker.setText(blkContents); + + macroName = blkContents.substring(0, charBreaker.next()); + + state.dataPrompter.accept("Enter macro body (. to end body)"); + while(source.hasNext()) { + blk = source.next(); + + blkContents = blk.contents.trim(); + + if(blkContents.equals(".")) break; + + macroBody.add(new Block(blk.blockNo, blkContents, blk.startLine, blk.endLine)); + } + + macroStore.put(macroName, macroBody); + + state.dataPrompter.accept(state.defaultPrompt); } /** @@ -64,7 +112,7 @@ public class FDS { public static S runFDS(FDSState state) throws FDSException { BlockReader blockSource = state.comin; - while (blockSource.hasNext() && !state.modes.empty()) { + while(blockSource.hasNext() && !state.modes.empty()) { Block comBlock = blockSource.next(); handleCommandString(comBlock, state); @@ -75,22 +123,22 @@ public class FDS { private static void handleCommandString(Block comBlock, FDSState state) throws FDSException { String comString = comBlock.contents.trim(); + BreakIterator charBreaker = BreakIterator.getCharacterInstance(); + charBreaker.setText(comString); - switch (state.mode) { + switch(state.mode) { case INLINE: - if (comString.contains(" ")) { + if(comString.contains(" ")) { handleInlineCommand(comString.split(" "), state, comBlock); break; } case CHORD: - if (comString.length() > 1) { + if(StringUtils.graphemeCount(comString) > 1) { chordCommand(comBlock, state, comString); break; } case NORMAL: - handleCommand(comString.charAt(0), state); - break; - case CHARINLINE: + handleCommand(comString.substring(0, charBreaker.next()), state); break; default: throw new FDSException(String.format("Unknown input mode '%s'", state.mode)); @@ -101,11 +149,11 @@ public class FDS { throws FDSException { boolean dataInput = false; - for (int i = 0; i < commands.length; i++) { + for(int i = 0; i < commands.length; i++) { String strang = commands[i].trim(); - if (dataInput) { - if (strang.equals(";")) { + if(dataInput) { + if(strang.equals(";")) { dataInput = false; } else { Block dataBlock = new Block(comBlock.blockNo + i, strang, comBlock.startLine, @@ -124,42 +172,49 @@ public class FDS { private static void chordCommand(Block comBlock, FDSState state, String comString) throws FDSException { PushbackBlockReader source = state.comin; - for (int i = 0; i < comString.length(); i++) { - char c = comString.charAt(i); + BreakIterator charBreaker = BreakIterator.getCharacterInstance(); + charBreaker.setText(comString); - Block newCom = new Block(comBlock.blockNo + 1, Character.toString(c), comBlock.startLine, - comBlock.startLine); + int lastPos = charBreaker.first(); + + while(charBreaker.next() != BreakIterator.DONE && lastPos < comString.length()) { + String c = comString.substring(lastPos, charBreaker.current()); + + Block newCom = new Block(comBlock.blockNo + 1, c, comBlock.startLine, comBlock.startLine); source.addBlock(newCom); + + lastPos = charBreaker.current(); } } @SuppressWarnings("unchecked") - private static void handleCommand(char com, FDSState state) throws FDSException { - if (state.modes.empty()) return; + private static void handleCommand(String com, FDSState state) throws FDSException { + if(state.modes.empty()) return; PrintStream printer = state.printer; /* * Handle built-in commands over user commands. */ - switch (com) { - case 'x': - if (state.mode == InputMode.CHORD) { + switch(com) { + case "x": + if(state.mode == InputMode.CHORD) { state.mode = InputMode.NORMAL; - } else if (state.mode == InputMode.NORMAL) { + } else if(state.mode == InputMode.NORMAL) { state.mode = InputMode.CHORD; } else { printer.println("? MNV\n"); } break; - case 'q': - state.modes.drop(); + case "q": + FDSMode md = state.modes.pop(); + printer.printf("!< %s\n", md.getName()); break; - case 'm': + case "m": helpSummary(printer, state); break; - case 'M': + case "M": /* * We'll see if this cast actually causes any issues. * @@ -167,13 +222,25 @@ public class FDS { * the state type, so it shouldn't matter. */ state.modes.push((FDSMode) miscMode); + printer.printf("!> MSC\n"); + break; + case "@": + state.modes.push(state.dataMacroMode); + printer.printf("!> IDM\n"); + break; + case "#": + state.modes.push(state.comMacroMode); + printer.printf("!> ICM\n"); break; default: FDSMode curMode = state.modes.top(); - if (curMode.hasSubmode(com)) { - state.modes.push(curMode.getSubmode(com)); - } else if (curMode.hasCommand(com)) { + if(curMode.hasSubmode(com)) { + FDSMode mode = curMode.getSubmode(com); + + state.modes.push(mode); + printer.printf("!> %s\n", mode.getName()); + } else if(curMode.hasCommand(com)) { curMode.getCommand(com).run(state); } else { printer.printf("? UBC '%s'\n", com); @@ -182,6 +249,8 @@ public class FDS { } private static void loadScript(FDSState state) { + state.dataPrompter.accept("Enter a filename: "); + String fileName = state.datain.next().contents.split(" ")[0]; PrintStream printer = state.printer; @@ -193,9 +262,11 @@ public class FDS { state.comin = pushback(serial(reader, state.comin)); state.datain = pushback(serial(reader, state.datain)); - } catch (FileNotFoundException fnfex) { + } catch(FileNotFoundException fnfex) { printer.printf("? FNF '%s'\n", fileName); } + + state.dataPrompter.accept(state.defaultPrompt); } private static void helpSummary(PrintStream printer, FDSState state) { @@ -203,18 +274,28 @@ public class FDS { printer.printf("Help for mode %s:\n", mode.getName()); - for (char bound : mode.registeredChars()) { + for(String bound : mode.registeredChars()) { Collection help = mode.getHelp(bound); - if (help.size() > 1) { - for (CommandHelp hlp : help) { - printer.printf("%s\t-\t%s", hlp.getSummary()); + if(help.size() > 1) { + for(CommandHelp hlp : help) { + printer.printf("\t%s\t- %s\n", bound, hlp.getSummary()); } + + printer.println(); } else { CommandHelp hlp = help.iterator().next(); - printer.printf("%s\t-\t%s", hlp.getSummary()); + printer.printf("\t%s\t- %s\n", bound, hlp.getSummary()); } } + + printer.printf("\nHelp for global commands:\n"); + printer.printf("\tx\t- chord mode\tread each character as a seperate command\n"); + printer.printf("\tq\t- exit mode\texit the current submode\n"); + printer.printf("\tm\t- show help\tshow this help message\n"); + printer.printf("\tM\t- misc. mode\tsubmode with misc. commands\n"); + printer.printf("\t@\t- invoke data macro\tinvoke bound data macros\n"); + printer.printf("\t#\t- invoke command macro\tinvoke bound command macros\n\n"); } } \ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSMode.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSMode.java index b59b345..60aeb38 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSMode.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSMode.java @@ -37,7 +37,7 @@ public interface FDSMode { * * @return All of the characters registered to something in this mode. */ - char[] registeredChars(); + String[] registeredChars(); /* * Check for the existence of commands/submodes. @@ -51,7 +51,7 @@ public interface FDSMode { * * @return Whether or not there is a command bound to that character. */ - boolean hasCommand(char c); + boolean hasCommand(String c); /** * Check if there is a submode registered to the given character. @@ -61,7 +61,7 @@ public interface FDSMode { * * @return Whether or not there is a submode bound to that character. */ - boolean hasSubmode(char c); + boolean hasSubmode(String c); /* * Get commands and submodes. @@ -78,7 +78,7 @@ public interface FDSMode { * @throws FDSException * If there is no command bound to that character. */ - FDSCommand getCommand(char c) throws FDSException; + FDSCommand getCommand(String c) throws FDSException; /** * Get the command attached to a given character. @@ -91,7 +91,7 @@ public interface FDSMode { * @throws FDSException * If there is no command bound to that character. */ - FDSMode getSubmode(char c) throws FDSException; + FDSMode getSubmode(String c) throws FDSException; /* * Help utilities @@ -106,7 +106,7 @@ public interface FDSMode { * * @return The help for what's bound to the character. */ - default Collection getHelp(char c) { + default Collection getHelp(String c) { return DEFAULT_HELP; } } diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSState.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSState.java index 1627b11..e28e6bc 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSState.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSState.java @@ -1,9 +1,14 @@ package bjc.utils.cli.fds; import java.io.PrintStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import bjc.utils.esodata.SimpleStack; import bjc.utils.esodata.Stack; +import bjc.utils.ioutils.Block; import bjc.utils.ioutils.PushbackBlockReader; /** @@ -39,15 +44,7 @@ public class FDSState { * * The semicolon can be escaped with a backslash. */ - INLINE, - /** - * Reads every character in the block, but after a terminal - * command, data will be read in-line with each character being - * a separate item until a semicolon is read. - * - * The semicolon can be escaped with a backslash. - */ - CHARINLINE, + INLINE; } /** @@ -79,6 +76,29 @@ public class FDSState { */ public PrintStream printer; + /** + * The repository for data macros. + */ + public Map> dataMacros; + + /** + * The repository for command macros. + */ + public Map> commandMacros; + + FDSMode dataMacroMode; + FDSMode comMacroMode; + + /** + * Function to change the current data prompt. + */ + public Consumer dataPrompter; + + /** + * The default data prompt. + */ + public String defaultPrompt; + /** * Create a new interface state. * @@ -95,9 +115,14 @@ public class FDSState { * * @param print * The destination for output. + * @param dataPrompt + * The function to use for changing the data prompt. + * + * @param normalPrompt + * The default data prompt. */ public FDSState(S stat, InputMode inputMode, PushbackBlockReader cmin, PushbackBlockReader datin, - PrintStream print) { + PrintStream print, Consumer dataPrompt, String normalPrompt) { state = stat; mode = inputMode; @@ -105,6 +130,15 @@ public class FDSState { datain = datin; printer = print; + dataPrompter = dataPrompt; + defaultPrompt = normalPrompt; + modes = new SimpleStack<>(); + + dataMacros = new HashMap<>(); + commandMacros = new HashMap<>(); + + dataMacroMode = new MacroFDSMode<>(dataMacros, datain::addBlock); + comMacroMode = new MacroFDSMode<>(commandMacros, comin::addBlock); } } \ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSUtils.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSUtils.java index f071671..a245471 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSUtils.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/FDSUtils.java @@ -51,7 +51,9 @@ public class FDSUtils { PushbackBlockReader comInput = pushback(rawComInput); PushbackBlockReader dataInput = pushback(rawDataInput); - FDSState fdsState = new FDSState<>(ctx, InputMode.INLINE, comInput, dataInput, out); + FDSState fdsState = new FDSState<>(ctx, InputMode.NORMAL, comInput, dataInput, out, + dataPrompter::setPrompt, "> "); + fdsState.modes.push(mode); FDS.runFDS(fdsState); diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/MacroFDSMode.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/MacroFDSMode.java new file mode 100644 index 0000000..2576ee5 --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/MacroFDSMode.java @@ -0,0 +1,79 @@ +package bjc.utils.cli.fds; + +import bjc.utils.ioutils.Block; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * A implementation of FDS mode that invokes macros bound into a map. + * + * @author EVE + * + * @param + * The FDS state type. + */ +public class MacroFDSMode implements FDSMode { + private final class MacroFDSCommand implements FDSCommand { + private String macroName; + + public MacroFDSCommand(String c) { + macroName = c; + } + + @Override + public void run(FDSState state) { + macros.get(macroName).forEach(dest); + } + } + + /* + * The available macros. + */ + private Map> macros; + + /* + * Where to send blocks from macros. + */ + private Consumer dest; + + /** + * Create a new FDS mode for macros. + * + * @param macros + * The macros to use. + * + * @param dest + * The destination for blocks from the macros. + */ + public MacroFDSMode(Map> macros, Consumer dest) { + this.macros = macros; + this.dest = dest; + } + + @Override + public String[] registeredChars() { + return macros.keySet().toArray(new String[0]); + } + + @Override + public boolean hasCommand(String comName) { + return macros.containsKey(comName); + } + + @Override + public boolean hasSubmode(String submodeName) { + return false; + } + + @Override + public FDSCommand getCommand(String comName) throws FDSException { + return new MacroFDSCommand(comName); + } + + @Override + public FDSMode getSubmode(String submodeName) throws FDSException { + throw new FDSException("Submodes aren't available in macroName modes"); + } +} \ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/SimpleFDSMode.java b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/SimpleFDSMode.java index 1dceb3a..8854d8e 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/fds/SimpleFDSMode.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/fds/SimpleFDSMode.java @@ -22,14 +22,16 @@ import static java.lang.String.format; * The FDS state type. */ public class SimpleFDSMode implements FDSMode { - private Map> commands; - private Map> modes; - private Multimap help; + private Map> commands; + private Map> modes; + private Multimap help; - private Set registered; - private char[] registeredArray; + private Set registered; + private String[] registeredArray; private boolean changed; + private String modeName; + /** * Create a new empty FDS mode. */ @@ -42,6 +44,26 @@ public class SimpleFDSMode implements FDSMode { changed = true; } + /** + * Create a new empty mode with a given name. + * + * @param name + * The name of the mode. + */ + public SimpleFDSMode(String name) { + this(); + + modeName = name; + } + + @Override + public String getName() { + if(modeName == null) + return FDSMode.super.getName(); + else + return modeName; + } + /** * Add a command to the mode. * @@ -57,17 +79,17 @@ public class SimpleFDSMode implements FDSMode { * @throws IllegalArgumentException * If the character is already bound to a command. */ - public void addCommand(char c, FDSCommand comm, CommandHelp hlp) { - if (comm == null) + public void addCommand(String c, FDSCommand comm, CommandHelp hlp) { + if(comm == null) throw new NullPointerException("Command must not be null"); - else if (commands.containsKey(c)) + else if(commands.containsKey(c)) throw new IllegalArgumentException(format("Character '%s' is already bound")); commands.put(c, comm); help.put(c, hlp); registered.add(c); - if (!changed) changed = true; + if(!changed) changed = true; } /** @@ -79,30 +101,33 @@ public class SimpleFDSMode implements FDSMode { * @param mode * The submode to add. * + * @param hlp + * The help for the submode. + * * @throws IllegalArgumentException * If the character is already bound to a submode. */ - public void addSubmode(char c, FDSMode mode, CommandHelp hlp) { - if (mode == null) + public void addSubmode(String c, FDSMode mode, CommandHelp hlp) { + if(mode == null) throw new NullPointerException("Mode must not be null"); - else if (modes.containsKey(c)) + else if(modes.containsKey(c)) throw new IllegalArgumentException(format("Character '%s' is already bound")); modes.put(c, mode); help.put(c, hlp); registered.add(c); - if (!changed) changed = true; + if(!changed) changed = true; } @Override - public char[] registeredChars() { - if (!changed) return registeredArray; + public String[] registeredChars() { + if(!changed) return registeredArray; - registeredArray = new char[registered.size()]; + registeredArray = new String[registered.size()]; int i = 0; - for (char c : registered) { + for(String c : registered) { registeredArray[i] = c; i += 1; @@ -114,18 +139,18 @@ public class SimpleFDSMode implements FDSMode { } @Override - public boolean hasCommand(char c) { + public boolean hasCommand(String c) { return commands.containsKey(c); } @Override - public boolean hasSubmode(char c) { + public boolean hasSubmode(String c) { return modes.containsKey(c); } @Override - public FDSCommand getCommand(char c) throws FDSException { - if (!commands.containsKey(c)) { + public FDSCommand getCommand(String c) throws FDSException { + if(!commands.containsKey(c)) { throw new FDSException(String.format("No command bound to '%s'", c)); } @@ -133,8 +158,8 @@ public class SimpleFDSMode implements FDSMode { } @Override - public FDSMode getSubmode(char c) throws FDSException { - if (!modes.containsKey(c)) { + public FDSMode getSubmode(String c) throws FDSException { + if(!modes.containsKey(c)) { throw new FDSException(String.format("No mode bound to '%s'", c)); } @@ -142,7 +167,7 @@ public class SimpleFDSMode implements FDSMode { } @Override - public Collection getHelp(char c) { + public Collection getHelp(String c) { return help.get(c); } } \ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/funcutils/StringUtils.java b/BJC-Utils2/src/main/java/bjc/utils/funcutils/StringUtils.java index a35253a..540fb39 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/funcutils/StringUtils.java +++ b/BJC-Utils2/src/main/java/bjc/utils/funcutils/StringUtils.java @@ -1,5 +1,7 @@ package bjc.utils.funcutils; +import com.ibm.icu.text.BreakIterator; + import java.util.Deque; /** @@ -22,9 +24,9 @@ public class StringUtils { * of the provided regex */ public static boolean containsOnly(String input, String regex) { - if (input == null) + if(input == null) throw new NullPointerException("Input must not be null"); - else if (regex == null) throw new NullPointerException("Regex must not be null"); + else if(regex == null) throw new NullPointerException("Regex must not be null"); /* * This regular expression is fairly simple. @@ -46,7 +48,7 @@ public class StringUtils { * The number of levels to indent */ public static void indentNLevels(StringBuilder builder, int levels) { - for (int i = 0; i < levels; i++) { + for(int i = 0; i < levels; i++) { builder.append("\t"); } } @@ -80,7 +82,7 @@ public class StringUtils { * @return The sequence as an English list. */ public static String toEnglishList(Object[] objects, String join, String comma) { - if (objects == null) { + if(objects == null) { throw new NullPointerException("Sequence must not be null"); } @@ -89,7 +91,7 @@ public class StringUtils { String joiner = join; String coma = comma; - switch (objects.length) { + switch(objects.length) { case 0: /* * Empty list. @@ -113,7 +115,7 @@ public class StringUtils { /* * Three or more items. */ - for (int i = 0; i < objects.length - 1; i++) { + for(int i = 0; i < objects.length - 1; i++) { sb.append(objects[i].toString()); sb.append(coma + " "); } @@ -157,10 +159,30 @@ public class StringUtils { * @return The sequence as an English list. */ public static String toEnglishList(Object[] objects, boolean and) { - if (and) { + if(and) { return toEnglishList(objects, "and"); } else { return toEnglishList(objects, "or"); } } -} + + /** + * Count the number of graphemes in a string. + * + * @param value + * The string to check. + * + * @return The number of graphemes in the string. + */ + public static int graphemeCount(String value) { + BreakIterator it = BreakIterator.getCharacterInstance(); + it.setText(value); + + int count = 0; + while(it.next() != BreakIterator.DONE) { + count++; + } + + return count; + } +} \ No newline at end of file -- cgit v1.2.3