Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a good zsh solution #69

Merged
merged 1 commit into from
Jul 27, 2014
Merged

Add a good zsh solution #69

merged 1 commit into from
Jul 27, 2014

Conversation

o11c
Copy link
Contributor

@o11c o11c commented Jul 27, 2014

No external commands!

Doesn't cheat by exiting early and then reading the tail of its own source like the other sh-family solutions.

(Also the readme should be updated sometime with the discoveries)

@eatnumber1
Copy link
Owner

Clever! I love it!

eatnumber1 pushed a commit that referenced this pull request Jul 27, 2014
Add a good zsh solution
@eatnumber1 eatnumber1 merged commit 883b849 into eatnumber1:master Jul 27, 2014
@eatnumber1
Copy link
Owner

If you want to add an explanation like what was written for the haskell one, I'll link to it from the editor's picks.

@o11c
Copy link
Contributor Author

o11c commented Jul 27, 2014

Shells are notorious for being quite sloppy with syntax, so when I saw bash and zsh in the Likely Impossible column.

At the time there were "complete" solutions in bash and sh that were very similar to each, that worked by opening the input file and then calling 'exit' before actually executing the goal lines, which is now considered an incomplete solution.

I knew that () were special to the shell, and that g()('al') was a function declaration that would execute a single command 'al'. (Most people use {a; b; c;} to define a function's body, but any group or single command works, subject to precedence).

It turns out that zsh allows anonymous functions, so g()()('al') is a function whose body is an anonymous function that in turn calls a command called 'al', and this makes nesting easy. Since g() will never actually execute anything other than creating the definition, we have to use a debug trap:

#!/usr/bin/zsh
debug_trap()
{
    echo $ZSH_DEBUG_CMD
}
trap debug_trap DEBUG
g('al');
g()('al')
g()()('al')
g()()()('al')

That prints g('al') but then hits ./goal.zsh:7: number expected and stops. I have no clue what that error means, and I don't care, since I'm interested in making the shell accept this code rather than actually doing something that makes sense to the shell.

But we know shells are forgiving, and our debug trap did get executed, so we can probably do something. It turns out that set -e has a special meaning inside a debug trap: it is used to skip execution of the command. Adding that, the basic debug trap outputs:

g('al')
g () {
        (
                'al'
        )
}
g () {
        () {
                (
                        'al'
                )
        }
}
g () {
        () {
                () {
                        (
                                'al'
                        )
                }
        }
}

Note that this is a restringified of what the shell parsed, not a direct source dump.

The (invalid) base case has no space after the g, but the valid ones do, so use the ${var/match/replace} syntax in the filter (since we want to still be able to execute normal shell stuff, otherwise we're no better than reading our own source code as input. The /replace can be omitted since it's being replaced with nothing. Then replace all (// instead of /) instances of (), as an exact string, with o, and strip out all the punctuation.

(Also there's a random al=1 line that's a remnant from an earlier attempt involving adding new math builtins, from when I was trying to follow the "don't print" rule)

@capicue capicue added the zsh label Jul 28, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants