No, the problem is that you are misunderstanding how pipes work.
In Unix pipelines, data flows from left to right (the same direction as written text in English) – the first program's output becomes the second program's input. But the pipes are unidirectional; the second program's output is not sent to the first program.
When you run sml file.sml | echo -e "\004", you aren't echoing Ctrl+D to sml. Instead, you are piping sml's output to the echo command – where it gets discarded, since echo does not use stdin.
- keyboard →
sml file.sml → echo -e "\004" → screen
Your second attempt, sml file.sml | sleep 2 ; echo -e "\004", has a similar problem – the output of sml is sent to sleep (which also discards the input it receives). However, there is another problem: the echo command now isn't part of the pipeline at all; you could say that | has a higher precedence than ;. (You can group commands using parentheses; e.g. a | (b; c; d).)
- keyboard →
sml file.sml → sleep 2 → screen
- keyboard →
echo -e "\004" → screen
As already said above, both attempts are written in the wrong direction. If you wanted to send the \004 character to sml, the pipeline should have been written like this:
echo -e "\004" | sml file.sml
in which, sml's output goes to the screen, where it should.
There is another problem, however. You are also confusing the CtrlD keypress with the "end of input" event.
The translation of CtrlD into "end of input" only happens at terminal device layer – in other words, only when you enter the Ctrl+D character into your terminal window. However, when you use echo -e "\004" in a pipe, it is not automagically translated to "end of input"; instead, the actual byte 004 [octal] is written.
If you want to actually tell the program that there is no more input, then do not write anything. Use a command that outputs nothing – for example, echo -n or true, or just redirect input from the /dev/null file.
echo -n | sml file.sml
sml file.sml < /dev/null