HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Morse code emitter

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
codeemittermorse

Problem

As the first step for writing a Morse code practice program, I thought I'd start with the very simplest step of a text-based Morse code translator. This simple program reads lines from stdin and emits them as a text-based representation of the Morse code equivalent. Any character that doesn't have a Morse code equivalent is simply skipped.

I'm interested particularly in a few things.

  • Is there a better representation than map that I should use?



  • Is there a way to declare it as constexpr?



  • Would this be better implemented as a facet?



The private emit member function is intended to be replaced by a mechanism that will queue up audio output.
Morse.cpp:

#include 
#include 
#include 
#include 

/* Morse code ITU-4 M.1677 */

class Morse
{
public:
    Morse &operator coding;
};

std::map Morse::coding = {
    {' ', " "},
    {'\n', "\n"},
    {'A', ".-"},
    {'B', "-..."},
    {'C', "-.-."},
    {'D', "-.."},
    {'E', "."},
    {'F', "..-."},
    {'G', "--."},
    {'H', "...."},
    {'I', ".."},
    {'J', ".---"},
    {'K', "-.-"},
    {'L', ".-.."},
    {'M', "--"},
    {'N', "-."},
    {'O', "---"},
    {'P', ".--."},
    {'Q', "--.-"},
    {'R', ".-."},
    {'S', "..."},
    {'T', "-"},
    {'U', "..-"},
    {'V', "...-"},
    {'W', ".--"},
    {'X', "-..-"},
    {'Y', "-.--"},
    {'Z', "--.."},
    {'1', ".----"},
    {'2', "..---"},
    {'3', "...--"},
    {'4', "....-"},
    {'5', "....."},
    {'6', "-...."},
    {'7', "--..."},
    {'8', "---.."},
    {'9', "----."},
    {'0', "-----"},
    {'.', ".-.-.-"},
    {',', "--..--"},
    {'?', "..--.."},
    {'-', "-...-"},
    {'/', "-..-."},
    {'@', ".--.-."},
    {'+', ".-.-."},
    {'=', "-..."},
    {'\'', ".----"},
    {'(', "-.--"},
    {')', "-.--."},
    {'\"', ".-..-"},
    {'\x04', "...-.-"}, //EOT = SK
};

int main()
{
    using namespace std;

    Morse morse;
    string line;
    while (getline(cin, line))
        morse << line << '\n';
}

Solution

The others have provided enough comments on the current code.

I find using facets relatively straight forward.

int main()
{
    std::locale         MorseLocal(std::locale::classic(), new ASCIItoMorse());

    /* Imbue std::cout before it is used */
    std::cout.imbue(MorseLocal);

    std::cout << "Line 1\nLine 2\nLine 3\n";

    /* You must imbue a file stream before it is opened. */
    std::ofstream       data;
    data.imbue(MorseLocal);
    data.open("PLOP");

    data << "Loki\n translated into morse rather than norse.";
}


Then the facet you want to specialize is codecvt

#include 
#include 
#include 
#include 

class ASCIItoMorse: public std::codecvt
{
  public:
   explicit ASCIItoMorse(size_t ref = 0): std::codecvt(ref)    {}

    typedef std::codecvt_base::result               result;
    typedef std::codecvt  parent;
    typedef parent::intern_type                     intern_type;
    typedef parent::extern_type                     extern_type;
    typedef parent::state_type                      state_type;
  protected:
    virtual result do_out(state_type& tabNeeded,
                         const intern_type* rStart, const intern_type*  rEnd, const intern_type*&   rNewStart,
                         extern_type*       wStart, extern_type*        wEnd, extern_type*&         wNewStart) const
    {
        result  res = std::codecvt_base::ok;

        for(;rStart  wEnd)
            {
                res = std::codecvt_base::partial;
                break;
            }
            wStart = std::copy(std::begin(output), std::end(output), wStart);
        }

        rNewStart   = rStart;
        wNewStart   = wStart;

        return res;
    }

    // Override so the do_out() virtual function is called.
    virtual bool do_always_noconv() const throw()  {return false;}
};


Output:

> ./a.out
.-.. .. -. .  .----
.-.. .. -. .  ..---
.-.. .. -. .  ...--
> cat PLOP
.-.. --- -.- ..
 - .-. .- -. ... .-.. .- - . -..  .. -. - ---  -- --- .-. ... .  .-. .- - .... . .-.  - .... .- -.  -. --- .-. ... . .-.-.-


Some examples of fun things to do with Facets and locale's:

Facet: numpunct

  • Is there a built-in function that comma-separates a number



  • How to insert spaces in a big number to make it more readable?



Facet: ctype

  • Is there a flag to make istream treat only tabs as delimiters?



  • How to tokenzie (words) classifying punctuation as space



Facet: codecvt

  • How to write only regularly spaced items from a char buffer to disk in C++



  • How can I count the number of characters that are printed as output?



  • ignore punctuation using manipulator



  • How to easily indent output to ofstream?



  • Writing utf16 to file in binary mode



  • Remove punctuation when reading a file

Code Snippets

int main()
{
    std::locale         MorseLocal(std::locale::classic(), new ASCIItoMorse());

    /* Imbue std::cout before it is used */
    std::cout.imbue(MorseLocal);

    std::cout << "Line 1\nLine 2\nLine 3\n";

    /* You must imbue a file stream before it is opened. */
    std::ofstream       data;
    data.imbue(MorseLocal);
    data.open("PLOP");

    data << "Loki\n translated into morse rather than norse.";
}
#include <locale>
#include <algorithm>
#include <iostream>
#include <fstream>

class ASCIItoMorse: public std::codecvt<char,char,std::mbstate_t>
{
  public:
   explicit ASCIItoMorse(size_t ref = 0): std::codecvt<char,char,std::mbstate_t>(ref)    {}

    typedef std::codecvt_base::result               result;
    typedef std::codecvt<char,char,std::mbstate_t>  parent;
    typedef parent::intern_type                     intern_type;
    typedef parent::extern_type                     extern_type;
    typedef parent::state_type                      state_type;
  protected:
    virtual result do_out(state_type& tabNeeded,
                         const intern_type* rStart, const intern_type*  rEnd, const intern_type*&   rNewStart,
                         extern_type*       wStart, extern_type*        wEnd, extern_type*&         wNewStart) const
    {
        result  res = std::codecvt_base::ok;

        for(;rStart < rEnd;++rStart)
        {
            // Get the output string
            // Note: I would add the trailing space into each `coding` value.
            std::string const&  output = coding[std::toupper(*rStart)];
            // Check we have room
            if (wStart+output.size() > wEnd)
            {
                res = std::codecvt_base::partial;
                break;
            }
            wStart = std::copy(std::begin(output), std::end(output), wStart);
        }

        rNewStart   = rStart;
        wNewStart   = wStart;

        return res;
    }

    // Override so the do_out() virtual function is called.
    virtual bool do_always_noconv() const throw()  {return false;}
};
> ./a.out
.-.. .. -. .  .----
.-.. .. -. .  ..---
.-.. .. -. .  ...--
> cat PLOP
.-.. --- -.- ..
 - .-. .- -. ... .-.. .- - . -..  .. -. - ---  -- --- .-. ... .  .-. .- - .... . .-.  - .... .- -.  -. --- .-. ... . .-.-.-

Context

StackExchange Code Review Q#71228, answer score: 12

Revisions (0)

No revisions yet.