Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/MorseCodeInterpreter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import token.Token;

import java.util.List;

/**
* @author Mark Nash
*
* The entry point to Morse Code interpreter. Call "interpret" from anywhere
* in your code.
*/
public class MorseCodeInterpreter {

/**
* A runtime exception that happened while tokenizing a string of
* Morse Code. The message of the exception further specifies the issue.
*/
static class MorseCodeRuntimeException extends RuntimeException {
MorseCodeRuntimeException(String message) {
super(message);
}
}

/**
* Reads the file, creates a list of tokens from the four token types,
* parses those tokens into English
* @param path A string of the file path to translate
* @return The interpreted string, null is returned if the file contained
* incorrect input
*/
public static String interpret(String path) {
if (path == null) {
throw new IllegalArgumentException(
"Morse Code file path cannot be null.");
}
List<Token> tokenList;
try {
tokenList = new MorseCodeTokenizer(path).tokenize();
} catch (Exception e) {
e.printStackTrace();
return null;
}
String interpretedString = new MorseCodeParser(tokenList).parse();
return interpretedString;
}

}
89 changes: 89 additions & 0 deletions src/MorseCodeParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import token.*;

import java.util.LinkedList;
import java.util.List;

/**
* @author Mark Nash
*
* Builds the string based on the tokens that have been read in
*/
public class MorseCodeParser {

/** The tokens that have been read in. */
private LinkedList<Token> tokenList;

/**
* Each index is a state and the data is the character that that state
* corresponds to.
*/
private static final char[] STATES_TO_CHARS = { '\0', 'e', 't', 'i', 'a',
'n', 'm', 's', 'u', 'r', 'w', 'd', 'k', 'g', 'o', 'h', 'v', 'f',
'\0', 'l', '\0', 'p', 'j', 'b', 'x', 'c', 'y', 'z', 'q', '\0' };

/** Store the token list. */
public MorseCodeParser(List<Token> tokenList) {
this.tokenList = (LinkedList<Token>)tokenList;
}

/**
* Create meaning out of the list of tokens
* @return The string that the list of tokens translates to
*/
public String parse() {
StringBuilder result = new StringBuilder();
for (Token token : tokenList) {
if (token instanceof CharToken) {
result.append(decodeCharacter(token.getValue()));
}
else if (token instanceof SpaceToken) {
result.append(token.getValue());
}
else if (token instanceof NewLineToken) {
result.append(token.getValue());
}
else {
// This is a CharSeparatorToken. The tokenizer has already
// separated the characters, so nothing needs to be done
// with this
}
}
return result.toString();
}

/**
* Determine the character from running a DFA state machine.
* An example of one can be found here:
* http://sound.whsites.net/articles/morse-f5.gif
* Each state is numbered starting from 0 going
* top to bottom, right to left.
*
* @param tokenValue A string of '.' and or '-'
* @return an ascii character that the Morse Code string corresponds to
*/
private char decodeCharacter(String tokenValue) {
int currState = 0;
for (char c : tokenValue.toCharArray()) {
switch (c) {
case '.':
currState = currState * 2 + 1;
break;
case '-':
currState = (currState + 1) * 2;
break;
default:
// this has already been dealt with during tokenizing
}
}
if (currState > 29) {
currState = 29;
}
char parsedChar = STATES_TO_CHARS[currState];

if (parsedChar == '\0') {
throw new MorseCodeInterpreter.MorseCodeRuntimeException(
"Illegal character: '" + tokenValue + "'");
}
return parsedChar;
}
}
107 changes: 107 additions & 0 deletions src/MorseCodeTokenizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import token.*;

import java.io.*;
import java.util.LinkedList;
import java.util.List;

/**
* @author Mark Nash
*
* Creates tokens for the whole file. The tokens are:
* || -> a character seperator
* |||| -> a space
* '\n' -> a newline
* '[.-]+' -> a character
*
* The four legal characters that can show up in a file are:
* '.', '-', '|', '\n'
*/
public class MorseCodeTokenizer {

/** The file reader. */
private BufferedReader reader;

/** An illegal character has been found while reading the file. */
private static class IllegalMorseCodeCharacterException extends Exception {
public IllegalMorseCodeCharacterException(char offendingCharacter) {
super("'" + offendingCharacter + "' " + "(" +
(int)offendingCharacter + ") is not a valid character. " +
"Only 4 characters are excepted: '.', '-', '|', and '\\n'");
}
}

/**
* Opens the file and creates a reader object
* @param filePath The path of the file to open
* @throws FileNotFoundException if the file does not exist.
*/
public MorseCodeTokenizer(String filePath) throws FileNotFoundException {
reader = new BufferedReader(new FileReader(filePath));
}

/**
* Reads the characters in the file and creates tokens out of them.
* @return A List of tokens from the reading of the file
* @throws MorseCodeInterpreter.MorseCodeRuntimeException A '|' character
* was read without another '|' with it
* @throws IllegalMorseCodeCharacterException An unaccepted character of the
* four characters was read
* @throws IOException Error closing the file or reading a character
*/
public List<Token> tokenize() throws
MorseCodeInterpreter.MorseCodeRuntimeException,
IllegalMorseCodeCharacterException,
IOException {

LinkedList<Token> tokenList = new LinkedList<>();
StringBuilder currChar = new StringBuilder();
int intChar;
char c;
while ((intChar = reader.read()) != -1) {
c = (char)intChar;
switch (c) {
case '.':
case '-':
currChar.append(c);
break;
case '|':
intChar = reader.read();
c = (char)intChar;
if (intChar == -1 || c != '|') {
throw new MorseCodeInterpreter.MorseCodeRuntimeException
("'|' is not a valid token");
}
if (tokenList.size() > 0 && currChar.length() == 0 &&
tokenList.getLast() instanceof CharSeparatorToken) {
tokenList.removeLast();
tokenList.add(new SpaceToken());
} else {
if (currChar.length() != 0) {
tokenList.add(new CharToken(currChar.toString()));
currChar = new StringBuilder();
}
tokenList.add(new CharSeparatorToken());
}
break;
case '\r':
break;
case '\n':
if (currChar.length() != 0) {
tokenList.add(new CharToken(currChar.toString()));
currChar = new StringBuilder();
}
tokenList.add(new NewLineToken());
break;
default:
throw new IllegalMorseCodeCharacterException(c);
}
}
if (currChar.length() != 0) {
tokenList.add(new CharToken(currChar.toString()));
}

reader.close();
return tokenList;
}

}
20 changes: 20 additions & 0 deletions src/token/CharSeparatorToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package token;

/**
* @author Mark Nash
*
* Separates characters in a Morse Code file.
*/
public class CharSeparatorToken implements Token {

@Override
public String getValue() {
return "||";
}

@Override
public String toString() {
return "CHAR_SEPERATOR";
}

}
26 changes: 26 additions & 0 deletions src/token/CharToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package token;

/**
* @author Mark Nash
*
* Holds a string of '.' and/or '-'.
*/
public class CharToken implements Token {

private String value;

public CharToken(String value) {
this.value = value;
}

@Override
public String getValue() {
return value;
}

@Override
public String toString() {
return "'" + value + "'";
}

}
20 changes: 20 additions & 0 deletions src/token/NewLineToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package token;

/**
* @author Mark Nash
*
* A newline token.
*/
public class NewLineToken implements Token {

@Override
public String getValue() {
return "\n";
}

@Override
public String toString() {
return "NEW_LINE";
}

}
20 changes: 20 additions & 0 deletions src/token/SpaceToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package token;

/**
* @author Mark Nash
*
* A token that represents a space between characters.
*/
public class SpaceToken implements Token {

@Override
public String getValue() {
return " ";
}

@Override
public String toString() {
return "SPACE";
}

}
12 changes: 12 additions & 0 deletions src/token/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package token;

/**
* @author Mark Nash
*
* A sort of token that has a semantic meaning. The data in an instance of one
* of these tokens is the meaning, and the name is the token type.
*/
public interface Token {

String getValue();
}
14 changes: 14 additions & 0 deletions test/MorseCodeInterpreterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
public class MorseCodeInterpreterTest {

public static void main(String[] args) {
for (String path : args) {
test(path);
}
}

private static void test(String path) {
System.out.println(path + " {\n" +
MorseCodeInterpreter.interpret(path) +
"\n}\n");
}
}
1 change: 1 addition & 0 deletions test/alphabet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.-||-...||-.-.||-..||.||..-.||--.||....||..||.---||-.-||.-..||--||-.||---||.--.||--.-||.-.||...||-||..-||...-||.--||-..-||-.--||--..
2 changes: 2 additions & 0 deletions test/given.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-..||---||--.
....||.||.-..||.-..||---||||.--||---||.-.||.-..||-..
4 changes: 4 additions & 0 deletions test/spacing.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

.-||-.
.-||||-.
.-||||||||-.
6 changes: 6 additions & 0 deletions test/stress.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@


..

.||.--.||||.-.||-.-||--||-..||.--.||||||||-
..||||....||---||.--.||.||||-.--||---||..-||||.-..||..||-.-||.||||--||-.--||||-.-.||---||-..||.