[
  {
    "path": ".gitignore",
    "content": "./cmd/cnt/cnt\n/cmd/nash/nash\n/coverage.txt\ncmd/nashfmt/nashfmt\ndist\n*.exe\nstdbin/mkdir/mkdir\nstdbin/pwd/pwd\nstdbin/strings/strings\nstdbin/write/write\ncoverage.html\n"
  },
  {
    "path": ".travis.yml",
    "content": "os:\n  - linux\n\nlanguage: go\nsudo: false\n\ngo:\n  - \"tip\"\n  - \"1.12\"\n\ninstall:\n  - go get -v golang.org/x/exp/ebnf\n  - make build\n\nscript:\n  - go get github.com/axw/gocov/gocov\n  - go get github.com/mattn/goveralls\n  - go get golang.org/x/tools/cmd/cover\n  - mkdir $HOME/nashroot\n  - make test\n  - make build\n  - ./cmd/nash/nash ./hack/releaser.sh testci\n\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n\nnotifications:\n  webhooks:\n    urls:\n      - https://webhooks.gitter.im/e/52ad02845e880cdca2cf\n    on_success: change\n    on_failure: always\n    on_start: never\n  email:\n    - tiago.natel@neoway.com.br\n    - tiagokatcipis@gmail.com\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.12\n\nADD . /go/src/github.com/madlambda/nash\n\nENV NASHPATH /nashpath\nENV NASHROOT /nashroot\n\nRUN cd /go/src/github.com/madlambda/nash && \\\n    make install\n\nCMD [\"/nashroot/bin/nash\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2016 Neoway Business Solution\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "ifndef version\nversion=$(shell git rev-list -1 HEAD)\nendif\n\nbuildargs = -ldflags \"-X main.VersionString=$(version)\" -v\n\nall: build test install\n\nbuild:\n\tcd cmd/nash && go build $(buildargs) \n\tcd cmd/nashfmt && go build $(buildargs) \n\tcd stdbin/mkdir && go build $(buildargs)\n\tcd stdbin/pwd && go build $(buildargs)\n\tcd stdbin/write && go build $(buildargs)\n\tcd stdbin/strings && go build $(buildargs)\n\nNASHPATH?=$(HOME)/nash\nNASHROOT?=$(HOME)/nashroot\n\n# FIXME: binaries install do not work on windows this way (the .exe)\ninstall: build\n\t@echo\n\t@echo \"Installing nash at: \"$(NASHROOT)\n\tmkdir -p $(NASHROOT)/bin\n\trm -f $(NASHROOT)/bin/nash\n\trm -f $(NASHROOT)/bin/nashfmt\n\tcp -p ./cmd/nash/nash $(NASHROOT)/bin\n\tcp -p ./cmd/nashfmt/nashfmt $(NASHROOT)/bin\n\trm -rf $(NASHROOT)/stdlib\n\tcp -pr ./stdlib $(NASHROOT)/stdlib\n\tcp -pr ./stdbin/mkdir/mkdir $(NASHROOT)/bin/mkdir\n\tcp -pr ./stdbin/pwd/pwd $(NASHROOT)/bin/pwd\n\tcp -pr ./stdbin/write/write $(NASHROOT)/bin/write\n\tcp -pr ./stdbin/strings/strings $(NASHROOT)/bin/strings\n\ndocsdeps:\n\tgo get github.com/madlambda/mdtoc/cmd/mdtoc\n\ndocs: docsdeps\n\tmdtoc -w ./README.md\n\tmdtoc -w ./docs/interactive.md\n\tmdtoc -w ./docs/reference.md\n\tmdtoc -w ./docs/stdlib/fmt.md\n\ntest: build\n\t./hack/check.sh\n\nupdate-vendor:\n\tcd cmd/nash && nash ./vendor.sh\n\nrelease: clean\n\t./hack/releaser.sh $(version)\n\ncoverage-html: test\n\tgo tool cover -html=coverage.txt -o coverage.html\n\t@echo \"coverage file: coverage.html\"\n\ncoverage-show: coverage-html\n\txdg-open coverage.html\n\nclean:\n\trm -f cmd/nash/nash\n\trm -f cmd/nashfmt/nashfmt\n\trm -rf dist\n"
  },
  {
    "path": "README.md",
    "content": "<!-- mdtocstart -->\n\n# Table of Contents\n\n- [nash](#nash)\n- [Show time!](#show-time)\n    - [Useful stuff](#useful-stuff)\n    - [Why nash scripts are reliable?](#why-nash-scripts-are-reliable)\n    - [Installation](#installation)\n        - [Release](#release)\n            - [Linux](#linux)\n        - [Master](#master)\n    - [Getting started](#getting-started)\n- [Accessing command line args](#accessing-command-line-args)\n- [Namespace features](#namespace-features)\n- [OK, but how scripts should look like?](#ok-but-how-scripts-should-look-like)\n- [Didn't work?](#didnt-work)\n- [Language specification](#language-specification)\n- [Some Bash comparisons](#some-bash-comparisons)\n- [Security](#security)\n- [Installing libraries](#installing-libraries)\n- [Releasing](#releasing)\n- [Want to contribute?](#want-to-contribute)\n\n<!-- mdtocend -->\n\n# nash\n\n[![Join the chat at https://gitter.im/madlambda/nash](https://badges.gitter.im/madlambda/nash.svg)](https://gitter.im/madlambda/nash?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/madlambda/nash?status.svg)](https://godoc.org/github.com/madlambda/nash)\n[![Build Status](https://travis-ci.org/madlambda/nash.svg?branch=master)](https://travis-ci.org/madlambda/nash) [![Go Report Card](https://goreportcard.com/badge/github.com/madlambda/nash)](https://goreportcard.com/report/github.com/madlambda/nash)\n\nNash is a system shell, inspired by plan9 `rc`, that makes it easy to create reliable and safe scripts taking advantages of operating systems namespaces (on linux and plan9) in an idiomatic way.\n\n\n## Useful stuff\n\n- nashfmt: Formats nash code (like gofmt) but no code styling defined yet (see Installation section).\n- [nashcomplete](https://github.com/madlambda/nashcomplete): Autocomplete done in nash script.\n- [Dotnash](https://github.com/lborguetti/dotnash): Nash profile customizations (e.g: prompt, aliases, etc)\n- [nash-mode](https://github.com/tiago4orion/nash-mode.el): Emacs major mode integrated with `nashfmt`.\n\n## Why nash scripts are reliable?\n\n1. Nash aborts at first non-success status of commands;\n2. Nash aborts at first unbound variable;\n3. It's possible to check the result status of every component of a pipe;\n4. **no eval**;\n5. Strings are pure strings (no evaluation of variables);\n6. No wildcards (globbing) of files; ('rm \\*' removes a file called '\\*');\n    - On windows, the terminal does the globbing when in interactive mode.\n    - On unix there's libs/completions to achieve something similar.\n7. No [obscure](http://explainshell.com/) syntax;\n8. Support tooling for indent/format and statically analyze the scripts;\n\n## Installation\n\nNash uses two environment variables: **NASHROOT** to find the standard nash library and **NASHPATH** to find libraries in general (like user's code).\n\nIt is important to have two different paths since this will allow you\nto upgrade nash (overwrite nash stdlib) without risking lost your code.\n\nIf **NASHPATH** is not set, a default of $HOME/nash will be assumed \n($HOMEPATH/nash on windows).\nIf **NASHROOT** is not set, a default of $HOME/nashroot will be assumed \n($HOMEPATH/nashroot on windows).\n\nThe libraries lookup dir will be $NASHPATH/lib.\nThe standard library lookup dir will be $NASHROOT/stdlib.\n\nAfter installing the nash binary will be located at $NASHROOT/bin.\n\n### Installing a Release\n\nInstalling is so stupid that we provide small scripts to do it.\nIf your platform is not supported take a look at the existent ones\nand send a MR with the script for your platform.\n\n#### Unix\n\nIf you run a unix based machine (Linux, Darwin/OSX, *BSD, etc)\nyou can use the script below:\n\nRun:\n\n```\n./hack/install/unix.sh\n```\n\n### Master\n\nRun:\n\n```\nmake install\n```\n\n## Getting started\n\nNash syntax resembles a common shell:\n\n```\nnash\nλ> echo \"hello world\"\nhello world\n```\nPipes works like borne shell and derivations:\n\n```sh\nλ> cat spec.ebnf | wc -l\n108\n```\nOutput redirection works like Plan9 rc, but not only for filenames. It\nsupports output redirection to tcp, udp and unix network protocols \n(unix sockets are not supported on windows).\n\n```sh\n# stdout to log.out, stderr to log.err\nλ> ./daemon >[1] log.out >[2] log.err\n# stderr pointing to stdout\nλ> ./daemon-logall >[2=1]\n# stdout to /dev/null\nλ> ./daemon-quiet >[1=]\n# stdout and stderr to tcp address\nλ> ./daemon >[1] \"udp://syslog:6666\" >[2=1]\n# stdout to unix file\nλ> ./daemon >[1] \"unix:///tmp/syslog.sock\"\n```\n\n**For safety, there's no `eval` or `string/tilde expansion` or `command substitution` in Nash.**\n\nTo assign command output to a variable exists the '<=' operator. See the example\nbelow:\n```sh\nvar fullpath <= realpath $path | xargs -n echo\necho $fullpath\n```\nThe symbol '<=' redirects the stdout of the command or function invocation in the\nright-hand side to the variable name specified.\n\nIf you want the command output splited into an array, then you'll need\nto store it in a temporary variable and then use the builtin `split` function.\n\n```sh\nvar out <= find .\nvar files <= split($out, \"\\n\")\n\nfor f in $files {\n        echo \"File: \" + $f\n}\n```\n\nTo avoid problems with spaces in variables being passed as\nmultiple arguments to commands, nash pass the contents of each\nvariable as a single argument to the command. It works like\nenclosing every variable with quotes before executing the command.\nThen the following example do the right thing:\n\n```sh\nvar fullname = \"John Nash\"\n./ci-register --name $fullname --option somevalue\n```\nOn bash you need to enclose the `$fullname` variable in quotes to avoid problems.\n\nNash syntax does not support shell expansion from strings. There's no way to\ndo things like the following in nash:\n\n```bash\necho \"The date is: $(date +%D)\" # DOESNT WORKS!\n```\n\nInstead you need to assign each command output to a proper variable and then\nconcat it with another string when needed (see the [reference docs](./docs/reference.md)).\n\nIn the same way, nash doesn't support shell expansion at `if` condition.\nFor check if a directory exists you must use:\n```sh\n-test -d $rootfsDir    # if you forget '-', the script will be aborted here\n                       # if path not exists\n\nif $status != \"0\" {\n        echo \"RootFS does not exists.\"\n        exit $status\n}\n```\nNash stops executing the script at first error found and, in the majority of times, it is what\nyou want (specially for deploys). But Commands have an explicitly\nway to bypass such restriction by prepending a dash '-' to the command statement.\nFor example:\n\n```sh\nfn cleanup()\n        -rm -rf $buildDir\n        -rm -rf $tmpDir\n}\n```\n\nThe dash '-' works only for operating system commands, other kind of errors are impossible to bypass.\nFor example, trying to evaluate an unbound variable aborts the program with error.\n\n```sh\nλ> echo $PATH\n/bin:/sbin:/usr/bin:/usr/local/bin:/home/user/.local/bin:/home/user/bin:/home/user/.gvm/pkgsets/go1.5.3/global/bin:/home/user/projects/3rdparty/plan9port/bin:/home/user/.gvm/gos/go1.5.3/bin\nλ> echo $bleh\nERROR: Variable '$bleh' not set\n```\n\nLong commands can be split in multiple lines:\n\n```sh\nλ> (aws ec2 attach-internet-gateway\t--internet-gateway-id $igwid\n\t\t\t\t\t\t\t\t\t--vpc-id $vpcid)\n\nλ> var instanceId <= (\n\taws ec2 run-instances\n\t\t\t--image-id ami-xxxxxxxx\n\t\t\t--count 1\n\t\t\t--instance-type t1.micro\n\t\t\t--key-name MyKeyPair\n\t\t\t--security-groups my-sg\n    | jq \".Instances[0].InstanceId\"\n)\nλ> echo $instanceId\n```\n\n# Accessing command line args\n\nWhen you run a nash script like:\n\n```\nλ> nash ./examples/args.sh --arg value\n```\n\nYou can get the args using the **ARGS** variable, that is a list:\n\n```\n#!/usr/bin/env nash\n\necho \"iterating through the arguments list\"\necho \"\"\nfor arg in $ARGS {\n\techo $arg\n}\n```\n\n\n# Namespace features\n\nNash is built with namespace support only on Linux (Plan9 soon). If\nyou use OSX, BSD or Windows, then the `rfork` keyword will fail.\n\n*The examples below assume you are on a Linux box.*\n\nBelow are some facilities for namespace management inside nash.\nMake sure you have USER namespaces enabled in your kernel:\n\n```sh\nzgrep CONFIG_USER_NS /proc/config.gz\nCONFIG_USER_NS=y\n```\n\nIf it's not enabled you will need root privileges to execute every example below...\n\nCreating a new process in a new USER namespace (u):\n\n```sh\nλ> id\nuid=1000(user) gid=1000(user) groups=1000(user),98(qubes)\nλ> rfork u {\n     id\n}\nuid=0(root) gid=0(root) groups=0(root),65534\n```\nYes, Linux supports creation of containers by unprivileged users. Tell\nthis to the customer success of your container-infrastructure-vendor. :-)\n\nThe default UID mapping is: Current UID (getuid) => 0 (no\nrange support). I'll look into more options for this in the future.\n\nYes, you can create multiple nested user namespaces. But kernel limits\nthe number of nested user namespace clones to 32.\n\n```sh\nλ> rfork u {\n    echo \"inside first container\"\n\n    id\n\n    rfork u {\n        echo \"inside second namespace...\"\n\n        id\n    }\n}\n```\n\nYou can verify that other types of namespace still requires root\ncapabilities, see for PID namespaces (p).\n\n```sh\nλ> rfork p {\n    id\n}\nERROR: fork/exec ./nash: operation not permitted\n```\n\nThe same happens for mount (m), ipc (i) and uts (s) if used without\nuser namespace (u) flag.\n\nThe `c` flag stands for \"container\" and is an alias for upmnis (all\ntypes of namespaces).  If you want another shell (maybe bash) inside\nthe namespace:\n\n```sh\nλ> rfork c {\n    bash\n}\n[root@stay-away nash]# id\nuid=0(root) gid=0(root) groups=0(root),65534\n[root@stay-away nash]# mount -t proc proc /proc\n[root@stay-away nash]#\n[root@stay-away nash]# ps aux\nUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND\nroot         1  0.0  0.0  34648  2748 pts/4    Sl   17:32   0:00 -rcd- -addr /tmp/nash.qNQa.sock\nroot         5  0.0  0.0  16028  3840 pts/4    S    17:32   0:00 /usr/bin/bash\nroot        23  0.0  0.0  34436  3056 pts/4    R+   17:34   0:00 ps aux\n```\n\nEverything except the `rfork` is like a common shell. Rfork will spawn a\nnew process with the namespace flags and executes the commands inside\nthe block on this namespace. It has the form:\n\n```sh\nrfork <flags> {\n    <statements to run inside the container>\n}\n```\n\n# OK, but how scripts should look like?\n\nSee the project [nash-app-example](https://github.com/madlambda/nash-app-example).\n\n# Didn't work?\n\nI've tested in the following environments:\n\n    Linux 4.7-rc7\n    Archlinux\n\n    Linux 4.5.5 (amd64)\n    Archlinux\n\n    Linux 4.3.3 (amd64)\n    Archlinux\n\n    Linux 4.1.13 (amd64)\n    Fedora release 23\n\n    Linux 4.1.13 (amd64)\n    Debian 8\n\n# Language specification\n\nThe specification isn't complete yet, but can be found\n[here](https://github.com/madlambda/nash/blob/master/spec.ebnf).\nThe file `spec_test.go` makes sure it is sane.\n\n# Some Bash comparisons\n\n| Bash | Nash | Description\t|\n| --- | --- | --- |\n| `GOPATH=/home/user/gopath` | `GOPATH=\"/home/user/gopath\"` | Nash enforces quoted strings |\n| `GOPATH=\"$HOME/gopath\"` | `GOPATH=$HOME+\"/gopath\"` | Nash doesn't do string expansion |\n| `export PATH=/usr/bin` | `PATH=\"/usr/bin\"`<br>`setenv PATH` | setenv operates only on valid variables |\n| `export` | `showenv` | |\n| `ls -la` | `ls -la` | Simple commads are identical |\n| `ls -la \"$GOPATH\"` | `ls -la $GOPATH` | Nash variables shouldn't be enclosed in quotes, because it's default behaviour |\n| `./worker 2>log.err 1>log.out` | `./worker >[2] log.err >[1] log.out` | Nash redirection works like plan9 rc |\n| `./worker 2>&1` | `./worker >[2=1]` | Redirection map only works for standard file descriptors (0,1,2) |\n\n# Security\n\nThe PID 1 of every namespace created by `nash` is the same nash binary reading\ncommands from the parent shell via unix socket. It allows the parent namespace\n(the script that creates the namespace) to issue commands inside the child\nnamespace. In the current implementation the unix socket communication is not\nsecure yet.\n\n# Installing libraries\n\nLets say you have a nash library and you want to install it. For example you have\nthe following:\n\n```\nawesome/code.sh\n```\n\nAnd you want to install it so you can write code like this:\n\n```\nimport awesome/code\n\ncode_do_awesome_stuff()\n```\n\nAll you have to do is run:\n\n```\nnash -install ./awesome\n```\n\nOr:\n\n```\nnash -install /absolute/path/awesome\n```\n\nThe entire awesome dir (and its subdirs) will be copied where nash\nsearches for libraries (dependent on environment variables).\n\nThis is the recommended way of installing nash libraries (althought\nyou can do it manually if you want).\n\nSingle files can also be installed as packages, for example:\n\n```\nnash -install ./awesome/code.sh\n```\n\nWill enable you to import like this:\n\n```\nimport code\n```\n\nIf there is already a package with the given name it will be\noverwritten.\n\n\n# Releasing\n\nTo generate a release basically:\n\n* Generate the release on github\n* Clone the generated tag\n* Run: ``` make release \"version=<version>\" ```\n\nWhere **<version>** must match the version of the git tag.\n\n# Want to contribute?\n\nOpen issues and PR :)\nThe project is in an early stage, be patient because things can change in the future.\n\n> \"What I cannot create, I do not understand.\"\n>\n> -- Richard Feynman\n"
  },
  {
    "path": "_disabled_appveyor.yml",
    "content": "version: \"1.0.0.{build}\"\nplatform: x64\nclone_folder: \"c:\\\\gopath\\\\src\\\\github.com\\\\madlambda\\\\nash\"\nenvironment: \n  GOPATH: \"c:\\\\gopath\"\ninstall: \n  - \"echo %PATH%\"\n  - \"echo %GOPATH%\"\n  - \"set PATH=%GOPATH%\\\\bin;c:\\\\go\\\\bin;c:\\\\MinGW\\\\bin;%PATH%\"\n  - \"go version\"\n  - \"go env\"\n  - copy c:\\MinGW\\bin\\mingw32-make.exe c:\\MinGW\\bin\\make.exe\n  - choco install cygwin\n  - set PATH=C:\\\\cygwin64\\\\bin;%PATH%\n\nbuild_script: \n  - make build\n  - make test\n\nnotifications:\n  - provider: GitHubPullRequest\n    auth_token:\n      secure: QuTLyXQp/4bQNeeEe5DLt9NIt/TzmZkn87s6wfOWpELX1L5UJyRCKV8AJitZWgwv\n    template: \"{{#passed}}:white_check_mark:{{/passed}}{{#failed}}:x:{{/failed}} [Build {{&projectName}} {{buildVersion}} {{status}}]({{buildUrl}}) (commit {{commitUrl}} by @{{&commitAuthorUsername}})\"\n"
  },
  {
    "path": "ast/doc_test.go",
    "content": "package ast_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/token\"\n)\n\nfunc Example_AssignmentNode() {\n\tone := ast.NewNameNode(token.NewFileInfo(1, 0), \"one\", nil)\n\ttwo := ast.NewNameNode(token.NewFileInfo(1, 4), \"two\", nil)\n\tvalue1 := ast.NewStringExpr(token.NewFileInfo(1, 8), \"1\", true)\n\tvalue2 := ast.NewStringExpr(token.NewFileInfo(1, 10), \"2\", true)\n\tassign := ast.NewAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{one, two},\n\t\t[]ast.Expr{value1, value2},\n\t)\n\n\tfmt.Printf(\"%s\", assign)\n\n\t// Output: one, two = \"1\", \"2\"\n}\n\nfunc Example_AssignmentNode_Single() {\n\toperatingSystems := ast.NewNameNode(token.NewFileInfo(1, 0), \"operatingSystems\", nil)\n\tvalues := []ast.Expr{\n\t\tast.NewStringExpr(token.NewFileInfo(1, 19), \"plan9 from bell labs\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(2, 19), \"unix\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(3, 19), \"linux\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(4, 19), \"oberon\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(5, 19), \"windows\", true),\n\t}\n\n\tlist := ast.NewListExpr(token.NewFileInfo(0, 18), values)\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\toperatingSystems,\n\t\tlist,\n\t)\n\n\tfmt.Printf(\"%s\", assign)\n\n\t// Output: operatingSystems = (\n\t// \t\"plan9 from bell labs\"\n\t// \t\"unix\"\n\t// \t\"linux\"\n\t// \t\"oberon\"\n\t// \t\"windows\"\n\t// )\n}\n"
  },
  {
    "path": "ast/node.go",
    "content": "package ast\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/madlambda/nash/token\"\n)\n\nconst (\n\t// RedirMapNoValue indicates the pipe has not redirection\n\tRedirMapNoValue = -1\n\t// RedirMapSupress indicates the rhs of map was suppressed\n\tRedirMapSupress = -2\n\n\tRforkFlags = \"umnips\"\n)\n\ntype (\n\t// Node represents nodes in the grammar\n\tNode interface {\n\t\tType() NodeType\n\t\tIsEqual(Node) bool\n\n\t\t// Line of node in the file\n\t\tLine() int\n\t\t// Column of the node in the file\n\t\tColumn() int\n\n\t\t// String representation of the node.\n\t\t// Note that it could not match the correspondent node in\n\t\t// the source code.\n\t\tString() string\n\t}\n\n\tassignable interface {\n\t\tnames() []*NameNode\n\t\tsetEqSpace(int)\n\t\tgetEqSpace() int\n\t\tstring() (string, bool)\n\t}\n\n\tegalitarian struct{}\n\n\t// Expr is the interface of expression nodes.\n\tExpr Node\n\n\t// NodeType is the types of grammar\n\tNodeType int\n\n\t// BlockNode is the block\n\tBlockNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tNodes []Node\n\t}\n\n\t// An ImportNode represents the node for an \"import\" keyword.\n\tImportNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tPath *StringExpr // Import path\n\t}\n\n\t// A SetenvNode represents the node for a \"setenv\" keyword.\n\tSetenvNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tName   string\n\t\tassign Node\n\t}\n\n\tNameNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tIdent string\n\t\tIndex Expr\n\t}\n\n\t// AssignNode is a node for variable assignments\n\tAssignNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tNames   []*NameNode\n\t\tValues  []Expr\n\t\teqSpace int\n\t}\n\n\t// ExecAssignNode represents the node for execution assignment.\n\tExecAssignNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tNames   []*NameNode\n\t\tcmd     Node\n\t\teqSpace int\n\t}\n\n\t// A CommandNode is a node for commands\n\tCommandNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tname   string\n\t\targs   []Expr\n\t\tredirs []*RedirectNode\n\n\t\tmulti bool\n\t}\n\n\t// PipeNode represents the node for a command pipeline.\n\tPipeNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tcmds  []*CommandNode\n\t\tmulti bool\n\t}\n\n\t// StringExpr is a string argument\n\tStringExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tstr    string\n\t\tquoted bool\n\t}\n\n\t// IntExpr is a integer used at indexing\n\tIntExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tval int\n\t}\n\n\t// ListExpr is a list argument\n\tListExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tList       []Expr\n\t\tIsVariadic bool\n\t}\n\n\t// ConcatExpr is a concatenation of arguments\n\tConcatExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tconcat []Expr\n\t}\n\n\t// VarExpr is a variable argument\n\tVarExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tName       string\n\t\tIsVariadic bool\n\t}\n\n\t// IndexExpr is a indexed variable\n\tIndexExpr struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tVar        *VarExpr\n\t\tIndex      Expr\n\t\tIsVariadic bool\n\t}\n\n\t// RedirectNode represents the output redirection part of a command\n\tRedirectNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\trmap     RedirMap\n\t\tlocation Expr\n\t}\n\n\t// RforkNode is a builtin node for rfork\n\tRforkNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\targ  *StringExpr\n\t\ttree *Tree\n\t}\n\n\t// CommentNode is the node for comments\n\tCommentNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tval string\n\t}\n\n\t// RedirMap is the map of file descriptors of the redirection\n\tRedirMap struct {\n\t\tlfd int\n\t\trfd int\n\t}\n\n\t// IfNode represents the node for the \"if\" keyword.\n\tIfNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tlvalue Expr\n\t\trvalue Expr\n\t\top     string\n\t\telseIf bool\n\n\t\tifTree   *Tree\n\t\telseTree *Tree\n\t}\n\n\t// VarAssignDeclNode is a \"var\" declaration to assign values\n\tVarAssignDeclNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tAssign *AssignNode\n\t}\n\n\t// VarExecAssignDeclNode is a var declaration to assign output of fn/cmd\n\tVarExecAssignDeclNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tExecAssign *ExecAssignNode\n\t}\n\n\t// FnArgNode represents function arguments\n\tFnArgNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tName       string\n\t\tIsVariadic bool\n\t}\n\n\t// A FnDeclNode represents a function declaration.\n\tFnDeclNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tname string\n\t\targs []*FnArgNode\n\t\ttree *Tree\n\t}\n\n\t// A FnInvNode represents a function invocation statement.\n\tFnInvNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tname string\n\t\targs []Expr\n\t}\n\n\t// A ReturnNode represents the \"return\" keyword.\n\tReturnNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tReturns []Expr\n\t}\n\n\t// A BindFnNode represents the \"bindfn\" keyword.\n\tBindFnNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tname    string\n\t\tcmdname string\n\t}\n\n\t// A ForNode represents the \"for\" keyword.\n\tForNode struct {\n\t\tNodeType\n\t\ttoken.FileInfo\n\t\tegalitarian\n\n\t\tidentifier string\n\t\tinExpr     Expr\n\t\ttree       *Tree\n\t}\n)\n\n//go:generate stringer -type=NodeType\n\nconst (\n\t// NodeSetenv is the type for \"setenv\" builtin keyword\n\tNodeSetenv NodeType = iota + 1\n\n\t// NodeBlock represents a program scope.\n\tNodeBlock\n\n\t// NodeName represents an identifier\n\tNodeName\n\n\t// NodeAssign is the type for variable assignment\n\tNodeAssign\n\n\t// NodeExecAssign is the type for command/function assignment\n\tNodeExecAssign\n\n\t// NodeImport is the type for \"import\" builtin keyword\n\tNodeImport\n\n\texecBegin\n\n\t// NodeCommand is the type for command execution\n\tNodeCommand\n\n\t// NodePipe is the type for pipeline execution\n\tNodePipe\n\n\t// NodeRedirect is the type for redirection nodes\n\tNodeRedirect\n\n\t// NodeFnInv is the type for function invocation\n\tNodeFnInv\n\n\texecEnd\n\n\texpressionBegin\n\n\t// NodeStringExpr is the type of string expression (quoted or not).\n\tNodeStringExpr\n\n\t// NodeIntExpr is the type of integer expression (commonly list indexing)\n\tNodeIntExpr\n\n\t// NodeVarExpr is the type of variable expressions.\n\tNodeVarExpr\n\n\t// NodeListExpr is the type of list expression.\n\tNodeListExpr\n\n\t// NodeIndexExpr is the type of indexing expressions.\n\tNodeIndexExpr\n\n\t// NodeConcatExpr is the type of concatenation expressions.\n\tNodeConcatExpr\n\n\texpressionEnd\n\n\t// NodeString are nodes for argument strings\n\tNodeString\n\n\t// NodeRfork is the type for rfork statement\n\tNodeRfork\n\n\t// NodeRforkFlags are nodes for rfork flags\n\tNodeRforkFlags\n\n\t// NodeIf is the type for if statements\n\tNodeIf\n\n\t// NodeComment are nodes for comment\n\tNodeComment\n\n\tNodeFnArg\n\n\t// NodeVarAssignDecl is the type for var declaration of values\n\tNodeVarAssignDecl\n\n\t// NodeVarExecAssignDecl\n\tNodeVarExecAssignDecl\n\n\t// NodeFnDecl is the type for function declaration\n\tNodeFnDecl\n\n\t// NodeReturn is the type for return statement\n\tNodeReturn\n\n\t// NodeBindFn is the type for bindfn statements\n\tNodeBindFn\n\n\t// NodeFor is the type for \"for\" statements\n\tNodeFor\n)\n\nvar (\n\tDebugCmp bool\n)\n\nfunc debug(format string, args ...interface{}) {\n\tif DebugCmp {\n\t\tfmt.Printf(\"[debug] \"+format+\"\\n\", args...)\n\t}\n}\n\n// Type returns the type of the node\nfunc (t NodeType) Type() NodeType {\n\treturn t\n}\n\n// IsExpr returns if the node is an expression.\nfunc (t NodeType) IsExpr() bool {\n\treturn t > expressionBegin && t < expressionEnd\n}\n\n// IsExecutable returns if the node is executable\nfunc (t NodeType) IsExecutable() bool {\n\treturn t > execBegin && t < execEnd\n}\n\nfunc (e egalitarian) equal(node, other Node) bool {\n\tif node == other {\n\t\treturn true\n\t}\n\n\tif node == nil {\n\t\treturn false\n\t}\n\n\tif !cmpInfo(node, other) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// NewBlockNode creates a new block\nfunc NewBlockNode(info token.FileInfo) *BlockNode {\n\treturn &BlockNode{\n\t\tNodeType: NodeBlock,\n\t\tFileInfo: info,\n\t}\n}\n\n// Push adds a new node for a block of nodes\nfunc (l *BlockNode) Push(n Node) {\n\tl.Nodes = append(l.Nodes, n)\n}\n\n// IsEqual returns if it is equal to the other node.\nfunc (l *BlockNode) IsEqual(other Node) bool {\n\tif !l.equal(l, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*BlockNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to cast other node to BlockNode\")\n\t\treturn false\n\t}\n\n\tif len(l.Nodes) != len(o.Nodes) {\n\t\tdebug(\"Nodes differs in length\")\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(l.Nodes); i++ {\n\t\tif !l.Nodes[i].IsEqual(o.Nodes[i]) {\n\t\t\tdebug(\"List entry %d differ... '%s' != '%s'\", i, l.Nodes[i], o.Nodes[i])\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// NewImportNode creates a new ImportNode object\nfunc NewImportNode(info token.FileInfo, path *StringExpr) *ImportNode {\n\treturn &ImportNode{\n\t\tNodeType: NodeImport,\n\t\tFileInfo: info,\n\n\t\tPath: path,\n\t}\n}\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *ImportNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ImportNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to cast to ImportNode\")\n\t\treturn false\n\t}\n\n\tif n.Path != o.Path {\n\t\tif n.Path != nil {\n\t\t\treturn n.Path.IsEqual(o.Path)\n\t\t}\n\t}\n\n\treturn false\n}\n\n// NewSetenvNode creates a new assignment node\nfunc NewSetenvNode(info token.FileInfo, name string, assign Node) (*SetenvNode, error) {\n\tif assign != nil && assign.Type() != NodeAssign &&\n\t\tassign.Type() != NodeExecAssign {\n\t\treturn nil, errors.New(\"Invalid assignment in setenv\")\n\t}\n\n\treturn &SetenvNode{\n\t\tNodeType: NodeSetenv,\n\t\tFileInfo: info,\n\n\t\tName:   name,\n\t\tassign: assign,\n\t}, nil\n}\n\n// Assignment returns the setenv assignment (if any)\nfunc (n *SetenvNode) Assignment() Node { return n.assign }\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *SetenvNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*SetenvNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to SetenvNode\")\n\t\treturn false\n\t}\n\n\tif n.assign != o.assign {\n\t\tif !n.assign.IsEqual(o.assign) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn n.Name == o.Name\n}\n\nfunc NewNameNode(info token.FileInfo, ident string, index Expr) *NameNode {\n\treturn &NameNode{\n\t\tNodeType: NodeName,\n\t\tFileInfo: info,\n\t\tIdent:    ident,\n\t\tIndex:    index,\n\t}\n}\n\nfunc (n *NameNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*NameNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to NameNode\")\n\t\treturn false\n\t}\n\n\tif n.Ident != o.Ident {\n\t\treturn false\n\t}\n\n\tif n.Index == o.Index {\n\t\treturn true\n\t}\n\n\tif n.Index != nil {\n\t\treturn n.Index.IsEqual(o.Index)\n\t}\n\n\treturn false\n}\n\n// NewAssignNode creates a new tuple assignment (multiple variable\n// assigned in a single statement).\n// For single assignment see NewSingleAssignNode.\nfunc NewAssignNode(info token.FileInfo, names []*NameNode, values []Expr) *AssignNode {\n\treturn &AssignNode{\n\t\tNodeType: NodeAssign,\n\t\tFileInfo: info,\n\t\teqSpace:  -1,\n\n\t\tNames:  names,\n\t\tValues: values,\n\t}\n}\n\n// NewSingleAssignNode creates an assignment of a single variable. Eg.:\n//   name = \"hello\"\n// To make an assignment of multiple variables in the same statement\n// use `NewAssignNode`.\nfunc NewSingleAssignNode(info token.FileInfo, name *NameNode, value Expr) *AssignNode {\n\treturn NewAssignNode(info, []*NameNode{name}, []Expr{value})\n}\n\n// TODO(i4k): fix that\nfunc (n *AssignNode) names() []*NameNode   { return n.Names }\nfunc (n *AssignNode) getEqSpace() int      { return n.eqSpace }\nfunc (n *AssignNode) setEqSpace(value int) { n.eqSpace = value }\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *AssignNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*AssignNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to AssignNode\")\n\t\treturn false\n\t}\n\n\tif len(n.Names) == len(o.Names) {\n\t\tfor i := 0; i < len(n.Names); i++ {\n\t\t\tif !n.Names[i].IsEqual(o.Names[i]) {\n\t\t\t\tdebug(\"Assignment identifier doesn't match: '%s' != '%s'\",\n\t\t\t\t\tn.Names[i], o.Names[i])\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t} else {\n\t\treturn false\n\t}\n\n\tif len(n.Values) == len(o.Values) {\n\t\tfor i := 0; i < len(n.Values); i++ {\n\t\t\tif !n.Values[i].IsEqual(o.Values[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t} else {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// NewExecAssignNode creates a new node for executing something and store the\n// result on a new variable. The assignment could be made using an operating system\n// command, a pipe of commands or a function invocation.\n// It returns a *ExecAssignNode ready to be executed or error when n is not a valid\n// node for execution.\n// TODO(i4k): Change the API to specific node types. Eg.: NewExecAssignCmdNode and\n// so on.\nfunc NewExecAssignNode(info token.FileInfo, names []*NameNode, n Node) (*ExecAssignNode, error) {\n\tif !n.Type().IsExecutable() {\n\t\treturn nil, errors.New(\"NewExecAssignNode expects a CommandNode, PipeNode or FninvNode\")\n\t}\n\n\treturn &ExecAssignNode{\n\t\tNodeType: NodeExecAssign,\n\t\tFileInfo: info,\n\n\t\tNames:   names,\n\t\tcmd:     n,\n\t\teqSpace: -1,\n\t}, nil\n}\n\nfunc (n *ExecAssignNode) names() []*NameNode   { return n.Names }\nfunc (n *ExecAssignNode) getEqSpace() int      { return n.eqSpace }\nfunc (n *ExecAssignNode) setEqSpace(value int) { n.eqSpace = value }\n\n// Command returns the command (or r-value). Command could be a CommandNode or FnNode\nfunc (n *ExecAssignNode) Command() Node {\n\treturn n.cmd\n}\n\n// SetCommand set the command part (NodeCommand or NodeFnDecl)\nfunc (n *ExecAssignNode) SetCommand(c Node) {\n\tn.cmd = c\n}\n\nfunc (n *ExecAssignNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ExecAssignNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to ExecAssignNode\")\n\t\treturn false\n\t}\n\n\tif len(n.Names) != len(o.Names) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.Names); i++ {\n\t\tif n.Names[i] != nil {\n\t\t\tif !n.Names[i].IsEqual(o.Names[i]) {\n\t\t\t\tdebug(\"Exec assignment name differs\")\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tif n.cmd == o.cmd {\n\t\treturn true\n\t} else if n.cmd != nil {\n\t\treturn n.cmd.IsEqual(o.cmd)\n\t}\n\n\treturn false\n}\n\n// NewCommandNode creates a new node for commands\nfunc NewCommandNode(info token.FileInfo, name string, multiline bool) *CommandNode {\n\treturn &CommandNode{\n\t\tNodeType: NodeCommand,\n\t\tFileInfo: info,\n\n\t\tname:  name,\n\t\tmulti: multiline,\n\t}\n}\n\nfunc (n *CommandNode) IsMulti() bool   { return n.multi }\nfunc (n *CommandNode) SetMulti(b bool) { n.multi = b }\n\n// AddArg adds a new argument to the command\nfunc (n *CommandNode) AddArg(a Expr) {\n\tn.args = append(n.args, a)\n}\n\n// SetArgs sets an array of args to command\nfunc (n *CommandNode) SetArgs(args []Expr) {\n\tn.args = args\n}\n\n// Args returns the list of arguments supplied to command.\nfunc (n *CommandNode) Args() []Expr { return n.args }\n\n// AddRedirect adds a new redirect node to command\nfunc (n *CommandNode) AddRedirect(redir *RedirectNode) {\n\tn.redirs = append(n.redirs, redir)\n}\n\n// Redirects return the list of redirect maps of the command.\nfunc (n *CommandNode) Redirects() []*RedirectNode { return n.redirs }\n\n// Name returns the program name\nfunc (n *CommandNode) Name() string { return n.name }\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *CommandNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*CommandNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to CommandNode\")\n\t\treturn false\n\t}\n\n\tif n.multi != o.multi {\n\t\tdebug(\"Command multiline differs.\")\n\t\treturn false\n\t}\n\n\tif len(n.args) != len(o.args) {\n\t\tdebug(\"Command argument length differs: %d (%+v) != %d (%+v)\",\n\t\t\tlen(n.args), n.args, len(o.args), o.args)\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tif !n.args[i].IsEqual(o.args[i]) {\n\t\t\tdebug(\"Argument %d differs. '%s' != '%s'\", i, n.args[i],\n\t\t\t\to.args[i])\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(n.redirs) != len(o.redirs) {\n\t\tdebug(\"Number of redirects differs. %d != %d\", len(n.redirs),\n\t\t\tlen(o.redirs))\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.redirs); i++ {\n\t\tif n.redirs[i] == o.redirs[i] {\n\t\t\tcontinue\n\t\t} else if n.redirs[i] != nil &&\n\t\t\t!n.redirs[i].IsEqual(o.redirs[i]) {\n\t\t\tdebug(\"Redirect differs... %s != %s\", n.redirs[i],\n\t\t\t\to.redirs[i])\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn n.name == o.name\n}\n\n// NewPipeNode creates a new command pipeline\nfunc NewPipeNode(info token.FileInfo, multi bool) *PipeNode {\n\treturn &PipeNode{\n\t\tNodeType: NodePipe,\n\t\tFileInfo: info,\n\n\t\tmulti: multi,\n\t}\n}\n\nfunc (n *PipeNode) IsMulti() bool   { return n.multi }\nfunc (n *PipeNode) SetMulti(b bool) { n.multi = b }\n\n// AddCmd add another command to end of the pipeline\nfunc (n *PipeNode) AddCmd(c *CommandNode) {\n\tn.cmds = append(n.cmds, c)\n}\n\n// Commands returns the list of pipeline commands\nfunc (n *PipeNode) Commands() []*CommandNode {\n\treturn n.cmds\n}\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *PipeNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*PipeNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to PipeNode\")\n\t\treturn false\n\t}\n\n\tif len(n.cmds) != len(o.cmds) {\n\t\tdebug(\"Number of pipe commands differ: %d != %d\",\n\t\t\tlen(n.cmds), len(o.cmds))\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.cmds); i++ {\n\t\tif !n.cmds[i].IsEqual(o.cmds[i]) {\n\t\t\tdebug(\"Command differs. '%s' != '%s'\", n.cmds[i],\n\t\t\t\to.cmds[i])\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// NewRedirectNode creates a new redirection node for commands\nfunc NewRedirectNode(info token.FileInfo) *RedirectNode {\n\treturn &RedirectNode{\n\t\tNodeType: NodeRedirect,\n\t\tFileInfo: info,\n\n\t\trmap: RedirMap{\n\t\t\tlfd: -1,\n\t\t\trfd: -1,\n\t\t},\n\t}\n}\n\n// SetMap sets the redirection map. Eg.: [2=1]\nfunc (r *RedirectNode) SetMap(lfd int, rfd int) {\n\tr.rmap.lfd = lfd\n\tr.rmap.rfd = rfd\n}\n\n// LeftFD return the lhs of the redirection map.\nfunc (r *RedirectNode) LeftFD() int { return r.rmap.lfd }\n\n// RightFD return the rhs of the redirection map.\nfunc (r *RedirectNode) RightFD() int { return r.rmap.rfd }\n\n// SetLocation of the output\nfunc (r *RedirectNode) SetLocation(s Expr) { r.location = s }\n\n// Location return the location of the redirection.\nfunc (r *RedirectNode) Location() Expr { return r.location }\n\n// IsEqual return if it is equal to the other node.\nfunc (r *RedirectNode) IsEqual(other Node) bool {\n\tif !r.equal(r, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*RedirectNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif r.rmap.lfd != o.rmap.lfd ||\n\t\tr.rmap.rfd != o.rmap.rfd {\n\t\treturn false\n\t}\n\n\tif r.location == o.location {\n\t\treturn true\n\t} else if r.location != nil {\n\t\treturn r.location.IsEqual(o.location)\n\t}\n\n\treturn false\n}\n\n// NewRforkNode creates a new node for rfork\nfunc NewRforkNode(info token.FileInfo) *RforkNode {\n\treturn &RforkNode{\n\t\tNodeType: NodeRfork,\n\t\tFileInfo: info,\n\t}\n}\n\n// Arg return the string argument of the rfork.\nfunc (n *RforkNode) Arg() *StringExpr {\n\treturn n.arg\n}\n\n// SetFlags sets the rfork flags\nfunc (n *RforkNode) SetFlags(a *StringExpr) {\n\tn.arg = a\n}\n\n// Tree returns the child tree of node\nfunc (n *RforkNode) Tree() *Tree {\n\treturn n.tree\n}\n\n// SetTree set the body of the rfork block.\nfunc (n *RforkNode) SetTree(t *Tree) {\n\tn.tree = t\n}\n\nfunc (n *RforkNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*RforkNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif n.arg == o.arg {\n\t\treturn true\n\t}\n\n\tif n.arg != nil {\n\t\tif !n.arg.IsEqual(o.arg) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn n.tree.IsEqual(o.tree)\n}\n\n// NewCommentNode creates a new node for comments\nfunc NewCommentNode(info token.FileInfo, val string) *CommentNode {\n\treturn &CommentNode{\n\t\tNodeType: NodeComment,\n\t\tFileInfo: info,\n\n\t\tval: val,\n\t}\n}\n\nfunc (n *CommentNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\tif n.Type() != other.Type() {\n\t\treturn false\n\t}\n\n\to, ok := other.(*CommentNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn n.val == o.val\n}\n\n// NewIfNode creates a new if block statement\nfunc NewIfNode(info token.FileInfo) *IfNode {\n\treturn &IfNode{\n\t\tNodeType: NodeIf,\n\t\tFileInfo: info,\n\t}\n}\n\n// Lvalue returns the lefthand part of condition\nfunc (n *IfNode) Lvalue() Expr {\n\treturn n.lvalue\n}\n\n// Rvalue returns the righthand side of condition\nfunc (n *IfNode) Rvalue() Expr {\n\treturn n.rvalue\n}\n\n// SetLvalue set the lefthand side of condition\nfunc (n *IfNode) SetLvalue(arg Expr) {\n\tn.lvalue = arg\n}\n\n// SetRvalue set the righthand side of condition\nfunc (n *IfNode) SetRvalue(arg Expr) {\n\tn.rvalue = arg\n}\n\n// Op returns the condition operation\nfunc (n *IfNode) Op() string { return n.op }\n\n// SetOp set the condition operation\nfunc (n *IfNode) SetOp(op string) {\n\tn.op = op\n}\n\n// IsElseIf tells if the if is an else-if statement\nfunc (n *IfNode) IsElseIf() bool {\n\treturn n.elseIf\n}\n\n// SetElseif sets the else-if part\nfunc (n *IfNode) SetElseif(b bool) {\n\tn.elseIf = b\n}\n\n// SetIfTree sets the block of statements of the if block\nfunc (n *IfNode) SetIfTree(t *Tree) {\n\tn.ifTree = t\n}\n\n// SetElseTree sets the block of statements of the else block\nfunc (n *IfNode) SetElseTree(t *Tree) {\n\tn.elseTree = t\n}\n\n// IfTree returns the if block\nfunc (n *IfNode) IfTree() *Tree { return n.ifTree }\n\n// ElseTree returns the else block\nfunc (n *IfNode) ElseTree() *Tree { return n.elseTree }\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *IfNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*IfNode)\n\n\tif !ok {\n\t\tdebug(\"Failed to convert to ifNode\")\n\t\treturn false\n\t}\n\n\telvalue := n.Lvalue()\n\tervalue := n.Rvalue()\n\tvlvalue := o.Lvalue()\n\tvrvalue := o.Rvalue()\n\n\tif !elvalue.IsEqual(vlvalue) {\n\t\tdebug(\"Lvalue differs: '%s' != '%s'\", elvalue, vlvalue)\n\t\treturn false\n\t}\n\n\tif !ervalue.IsEqual(vrvalue) {\n\t\tdebug(\"Rvalue differs: '%s' != '%s'\", ervalue, vrvalue)\n\t\treturn false\n\t}\n\n\tif n.Op() != o.Op() {\n\t\tdebug(\"Operation differs: %s != %s\", n.Op(), o.Op())\n\t\treturn false\n\t}\n\n\texpectedTree := n.IfTree()\n\tvalueTree := o.IfTree()\n\n\tif !expectedTree.IsEqual(valueTree) {\n\t\tdebug(\"If tree differs: '%s' != '%s'\", expectedTree,\n\t\t\tvalueTree)\n\t\treturn false\n\t}\n\n\texpectedTree = n.ElseTree()\n\tvalueTree = o.ElseTree()\n\n\treturn expectedTree.IsEqual(valueTree)\n}\n\nfunc NewFnArgNode(info token.FileInfo, name string, isVariadic bool) *FnArgNode {\n\treturn &FnArgNode{\n\t\tNodeType: NodeFnArg,\n\t\tFileInfo: info,\n\n\t\tName:       name,\n\t\tIsVariadic: isVariadic,\n\t}\n}\n\nfunc (a *FnArgNode) IsEqual(other Node) bool {\n\tif !a.equal(a, other) {\n\t\treturn false\n\t}\n\to, ok := other.(*FnArgNode)\n\tif !ok {\n\t\treturn false\n\t}\n\tif a.Name != o.Name ||\n\t\ta.IsVariadic != o.IsVariadic {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc NewVarAssignDecl(info token.FileInfo, assignNode *AssignNode) *VarAssignDeclNode {\n\treturn &VarAssignDeclNode{\n\t\tNodeType: NodeVarAssignDecl,\n\t\tAssign:   assignNode,\n\t}\n}\n\nfunc (n *VarAssignDeclNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*VarAssignDeclNode)\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn n.Assign.IsEqual(o.Assign)\n}\n\nfunc NewVarExecAssignDecl(info token.FileInfo, assignNode *ExecAssignNode) *VarExecAssignDeclNode {\n\treturn &VarExecAssignDeclNode{\n\t\tNodeType:   NodeVarExecAssignDecl,\n\t\tExecAssign: assignNode,\n\t}\n}\n\nfunc (n *VarExecAssignDeclNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*VarExecAssignDeclNode)\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn n.ExecAssign.IsEqual(o.ExecAssign)\n}\n\n// NewFnDeclNode creates a new function declaration\nfunc NewFnDeclNode(info token.FileInfo, name string) *FnDeclNode {\n\treturn &FnDeclNode{\n\t\tNodeType: NodeFnDecl,\n\t\tFileInfo: info,\n\n\t\tname: name,\n\t}\n}\n\n// SetName set the function name\nfunc (n *FnDeclNode) SetName(a string) {\n\tn.name = a\n}\n\n// Name return the function name\nfunc (n *FnDeclNode) Name() string {\n\treturn n.name\n}\n\n// Args returns function arguments\nfunc (n *FnDeclNode) Args() []*FnArgNode {\n\treturn n.args\n}\n\n// AddArg add a new argument to end of argument list\nfunc (n *FnDeclNode) AddArg(arg *FnArgNode) {\n\tn.args = append(n.args, arg)\n}\n\n// Tree return the function block\nfunc (n *FnDeclNode) Tree() *Tree {\n\treturn n.tree\n}\n\n// SetTree set the function tree\nfunc (n *FnDeclNode) SetTree(t *Tree) {\n\tn.tree = t\n}\n\nfunc (n *FnDeclNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*FnDeclNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif n.name != o.name || len(n.args) != len(o.args) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tif !n.args[i].IsEqual(o.args[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// NewFnInvNode creates a new function invocation\nfunc NewFnInvNode(info token.FileInfo, name string) *FnInvNode {\n\treturn &FnInvNode{\n\t\tNodeType: NodeFnInv,\n\t\tFileInfo: info,\n\n\t\tname: name,\n\t}\n}\n\n// SetName set the function name\nfunc (n *FnInvNode) SetName(a string) {\n\tn.name = a\n}\n\n// Name return the function name\nfunc (n *FnInvNode) Name() string {\n\treturn n.name\n}\n\n// AddArg add another argument to end of argument list\nfunc (n *FnInvNode) AddArg(arg Expr) {\n\tn.args = append(n.args, arg)\n}\n\n// Args return the invocation arguments.\nfunc (n *FnInvNode) Args() []Expr { return n.args }\n\n// IsEqual returns if it is equal to the other node.\nfunc (n *FnInvNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*FnInvNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif len(n.args) != len(o.args) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tif !n.args[i].IsEqual(o.args[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// NewBindFnNode creates a new bindfn statement\nfunc NewBindFnNode(info token.FileInfo, name, cmd string) *BindFnNode {\n\treturn &BindFnNode{\n\t\tNodeType: NodeBindFn,\n\t\tFileInfo: info,\n\n\t\tname:    name,\n\t\tcmdname: cmd,\n\t}\n}\n\n// Name return the function name\nfunc (n *BindFnNode) Name() string { return n.name }\n\n// CmdName return the command name\nfunc (n *BindFnNode) CmdName() string { return n.cmdname }\n\nfunc (n *BindFnNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*BindFnNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn n.name == o.name && n.cmdname == o.cmdname\n}\n\n// NewReturnNode create a return statement\nfunc NewReturnNode(info token.FileInfo) *ReturnNode {\n\treturn &ReturnNode{\n\t\tFileInfo: info,\n\t\tNodeType: NodeReturn,\n\t}\n}\n\nfunc (n *ReturnNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\tif n.Type() != other.Type() {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ReturnNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif len(n.Returns) != len(o.Returns) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(n.Returns); i++ {\n\t\targ := n.Returns[i]\n\t\toarg := o.Returns[i]\n\n\t\tif arg != nil && !arg.IsEqual(oarg) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// NewForNode create a new for statement\nfunc NewForNode(info token.FileInfo) *ForNode {\n\treturn &ForNode{\n\t\tNodeType: NodeFor,\n\t\tFileInfo: info,\n\t}\n}\n\n// SetIdentifier set the for indentifier\nfunc (n *ForNode) SetIdentifier(a string) {\n\tn.identifier = a\n}\n\n// Identifier return the identifier part\nfunc (n *ForNode) Identifier() string { return n.identifier }\n\n// InVar return the \"in\" variable\nfunc (n *ForNode) InExpr() Expr { return n.inExpr }\n\n// SetInVar set \"in\" expression\nfunc (n *ForNode) SetInExpr(a Expr) { n.inExpr = a }\n\n// SetTree set the for block of statements\nfunc (n *ForNode) SetTree(a *Tree) {\n\tn.tree = a\n}\n\n// Tree return the for block\nfunc (n *ForNode) Tree() *Tree { return n.tree }\n\nfunc (n *ForNode) IsEqual(other Node) bool {\n\tif !n.equal(n, other) {\n\t\treturn false\n\t}\n\n\tif n.Type() != other.Type() {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ForNode)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif n.identifier != o.identifier {\n\t\treturn false\n\t}\n\n\tif n.inExpr == o.inExpr {\n\t\treturn true\n\t}\n\n\tif n.inExpr != nil {\n\t\treturn n.inExpr.IsEqual(o.inExpr)\n\t}\n\n\treturn false\n}\n\nfunc cmpInfo(n, other Node) bool {\n\tif n.Line() != other.Line() ||\n\t\tn.Column() != other.Column() {\n\t\tdebug(\"file info mismatch on %v (%s): (%d, %d) != (%d, %d)\",\n\t\t\tn, n.Type(), n.Line(), n.Column(),\n\t\t\tother.Line(), other.Column())\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "ast/node_args.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/madlambda/nash/scanner\"\n\t\"github.com/madlambda/nash/token\"\n)\n\n// ArgFromToken is a helper to get an argument based on the lexer token\nfunc ExprFromToken(val scanner.Token) (Expr, error) {\n\tswitch val.Type() {\n\tcase token.Arg:\n\t\treturn NewStringExpr(token.NewFileInfo(val.Line(), val.Column()), val.Value(), false), nil\n\tcase token.String:\n\t\treturn NewStringExpr(token.NewFileInfo(val.Line(), val.Column()), val.Value(), true), nil\n\tcase token.Variable:\n\t\treturn NewVarExpr(token.NewFileInfo(val.Line(), val.Column()), val.Value()), nil\n\t}\n\n\treturn nil, fmt.Errorf(\"argFromToken doesn't support type %v\", val)\n}\n\n// NewArgString creates a new string argument\nfunc NewStringExpr(info token.FileInfo, value string, quoted bool) *StringExpr {\n\treturn &StringExpr{\n\t\tNodeType: NodeStringExpr,\n\t\tFileInfo: info,\n\n\t\tstr:    value,\n\t\tquoted: quoted,\n\t}\n}\n\n// Value returns the argument string value\nfunc (s *StringExpr) Value() string {\n\treturn s.str\n}\n\nfunc (s *StringExpr) SetValue(a string) {\n\ts.str = a\n}\n\nfunc (s *StringExpr) IsEqual(other Node) bool {\n\tif !s.equal(s, other) {\n\t\treturn false\n\t}\n\n\tvalue, ok := other.(*StringExpr)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif s.quoted != value.quoted {\n\t\treturn false\n\t}\n\n\treturn s.str == value.str\n}\n\nfunc NewIntExpr(info token.FileInfo, val int) *IntExpr {\n\treturn &IntExpr{\n\t\tNodeType: NodeIntExpr,\n\t\tFileInfo: info,\n\n\t\tval: val,\n\t}\n}\n\nfunc (i *IntExpr) Value() int { return i.val }\n\nfunc (i *IntExpr) IsEqual(other Node) bool {\n\tif !i.equal(i, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*IntExpr)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn i.val == o.val\n}\n\nfunc NewListExpr(info token.FileInfo, values []Expr) *ListExpr {\n\treturn NewListVariadicExpr(info, values, false)\n}\n\nfunc NewListVariadicExpr(info token.FileInfo, values []Expr, variadic bool) *ListExpr {\n\treturn &ListExpr{\n\t\tNodeType: NodeListExpr,\n\t\tFileInfo: info,\n\n\t\tList:       values,\n\t\tIsVariadic: variadic,\n\t}\n}\n\n// PushExpr push an expression to end of the list\nfunc (l *ListExpr) PushExpr(a Expr) {\n\tl.List = append(l.List, a)\n}\n\nfunc (l *ListExpr) IsEqual(other Node) bool {\n\tif !l.equal(l, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ListExpr)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif len(l.List) != len(o.List) {\n\t\treturn false\n\t}\n\n\tfor i, val := range l.List {\n\t\toval := o.List[i]\n\t\tif !val.IsEqual(oval) {\n\t\t\tdebug(\"%v(%s) != %v(%s)\", val, val.Type(),\n\t\t\t\toval, oval.Type())\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc NewConcatExpr(info token.FileInfo, parts []Expr) *ConcatExpr {\n\treturn &ConcatExpr{\n\t\tNodeType: NodeConcatExpr,\n\t\tFileInfo: info,\n\n\t\tconcat: parts,\n\t}\n}\n\n// PushExpr push an expression to end of the concat list\nfunc (c *ConcatExpr) PushExpr(a Expr) {\n\tc.concat = append(c.concat, a)\n}\n\n// SetConcatList set the concatenation parts\nfunc (c *ConcatExpr) SetConcat(v []Expr) {\n\tc.concat = v\n}\n\nfunc (c *ConcatExpr) List() []Expr { return c.concat }\n\nfunc (c *ConcatExpr) IsEqual(other Node) bool {\n\tif !c.equal(c, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*ConcatExpr)\n\n\tif !ok {\n\t\treturn false\n\t}\n\n\tif len(c.concat) != len(o.concat) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(c.concat); i++ {\n\t\tif !c.concat[i].IsEqual(o.concat[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc NewVarExpr(info token.FileInfo, name string) *VarExpr {\n\treturn NewVarVariadicExpr(info, name, false)\n}\n\nfunc NewVarVariadicExpr(info token.FileInfo, name string, isVariadic bool) *VarExpr {\n\treturn &VarExpr{\n\t\tNodeType:   NodeVarExpr,\n\t\tFileInfo:   info,\n\t\tName:       name,\n\t\tIsVariadic: isVariadic,\n\t}\n}\n\nfunc (v *VarExpr) IsEqual(other Node) bool {\n\tif !v.equal(v, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*VarExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn v.Name == o.Name &&\n\t\tv.IsVariadic == o.IsVariadic\n}\n\nfunc NewIndexExpr(info token.FileInfo, va *VarExpr, idx Expr) *IndexExpr {\n\treturn NewIndexVariadicExpr(info, va, idx, false)\n}\n\nfunc NewIndexVariadicExpr(info token.FileInfo, va *VarExpr, idx Expr, variadic bool) *IndexExpr {\n\treturn &IndexExpr{\n\t\tNodeType: NodeIndexExpr,\n\t\tFileInfo: info,\n\n\t\tVar:        va,\n\t\tIndex:      idx,\n\t\tIsVariadic: variadic,\n\t}\n}\n\nfunc (i *IndexExpr) IsEqual(other Node) bool {\n\tif !i.equal(i, other) {\n\t\treturn false\n\t}\n\n\to, ok := other.(*IndexExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn i.Var.IsEqual(o.Var) &&\n\t\ti.Index.IsEqual(o.Index) &&\n\t\ti.IsVariadic == o.IsVariadic\n}\n"
  },
  {
    "path": "ast/node_fmt.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc (s *StringExpr) String() string {\n\tif s.quoted {\n\t\treturn `\"` + stringify(s.str) + `\"`\n\t}\n\n\treturn s.str\n}\n\nfunc (i *IntExpr) String() string {\n\treturn strconv.Itoa(i.val)\n}\n\nfunc (l *ListExpr) string() (string, bool) {\n\telems := make([]string, len(l.List))\n\tcolumnCount := 0\n\tforceMulti := false\n\n\tfor i := 0; i < len(l.List); i++ {\n\t\tif l.List[i].Type() == NodeListExpr {\n\t\t\tforceMulti = true\n\t\t}\n\n\t\telems[i] = l.List[i].String()\n\t\tcolumnCount += len(elems[i])\n\t}\n\n\tif columnCount+len(elems) > 50 || forceMulti {\n\t\tforceMulti = true\n\t\treturn \"(\\n\\t\" + strings.Join(elems, \"\\n\\t\") + \"\\n)\", forceMulti\n\t}\n\n\treturn \"(\" + strings.Join(elems, \" \") + \")\", false\n}\n\nfunc (l *ListExpr) String() string {\n\tstr, _ := l.string()\n\treturn str\n}\n\nfunc (c *ConcatExpr) String() string {\n\tret := \"\"\n\n\tfor i := 0; i < len(c.concat); i++ {\n\t\tret += c.concat[i].String()\n\n\t\tif i < (len(c.concat) - 1) {\n\t\t\tret += \"+\"\n\t\t}\n\t}\n\n\treturn ret\n}\n\nfunc (v *VarExpr) String() string {\n\tif v.IsVariadic {\n\t\treturn v.Name + \"...\"\n\t}\n\treturn v.Name\n}\n\nfunc (i *IndexExpr) String() string {\n\tret := fmt.Sprintf(\"%s[%s]\", i.Var, i.Index)\n\tif i.IsVariadic {\n\t\treturn ret + \"...\"\n\t}\n\treturn ret\n}\n\nfunc (l *BlockNode) adjustGroupAssign(node assignable, nodes []Node) {\n\tvar (\n\t\teqSpace int = node.getEqSpace()\n\t\ti       int\n\t)\n\n\tlhs := getlhs(node)\n\n\teqSpace = len(lhs) + 1\n\n\tfor i = 0; i < len(nodes); i++ {\n\t\tassign, ok := nodes[i].(assignable)\n\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tif len(getlhs(assign))+1 > eqSpace {\n\t\t\teqSpace = len(getlhs(assign)) + 1\n\t\t}\n\t}\n\n\tfor j := 0; j < i; j++ {\n\t\tknode := nodes[j].(assignable)\n\t\tknode.setEqSpace(eqSpace)\n\t}\n\n\tnode.setEqSpace(eqSpace)\n}\n\nfunc (l *BlockNode) String() string {\n\tnodes := l.Nodes\n\tcontent := make([]string, 0, 8192)\n\n\tlast := (len(nodes) - 1)\n\n\tfor i := 0; i < len(nodes); i++ {\n\t\taddEOL := false\n\t\tnode := nodes[i]\n\n\t\tnodebytes := node.String()\n\n\t\tif i == 0 && node.Type() == NodeComment &&\n\t\t\tstrings.HasPrefix(node.String(), \"#!\") {\n\t\t\taddEOL = true\n\t\t} else if (node.Type() == NodeComment) && i < last {\n\t\t\tnextNode := nodes[i+1]\n\n\t\t\tif nextNode.Line() > node.Line()+1 {\n\t\t\t\taddEOL = true\n\t\t\t}\n\t\t} else if i < last {\n\t\t\tnextNode := nodes[i+1]\n\n\t\t\tif node.Type() != nextNode.Type() {\n\t\t\t\taddEOL = true\n\t\t\t} else if node.Type() == NodeFnDecl {\n\t\t\t\taddEOL = true\n\t\t\t} else if node.Type() == NodeAssign || node.Type() == NodeExecAssign {\n\t\t\t\tnodeAssign := node.(assignable)\n\n\t\t\t\tif nodeAssign.getEqSpace() == -1 {\n\t\t\t\t\t// lookahead to decide about best '=' distance\n\t\t\t\t\tl.adjustGroupAssign(nodeAssign, nodes[i+1:])\n\t\t\t\t}\n\n\t\t\t\tnodebytes, addEOL = nodeAssign.string()\n\t\t\t}\n\t\t}\n\n\t\tif addEOL {\n\t\t\tnodebytes += \"\\n\"\n\t\t}\n\n\t\tcontent = append(content, nodebytes)\n\t}\n\n\treturn strings.Join(content, \"\\n\")\n}\n\n// String returns the string representation of the import\nfunc (n *ImportNode) String() string {\n\treturn `import ` + n.Path.String()\n}\n\n// String returns the string representation of assignment\nfunc (n *SetenvNode) String() string {\n\tif n.assign == nil {\n\t\treturn \"setenv \" + n.Name\n\t}\n\n\treturn \"setenv \" + n.assign.String()\n}\n\nfunc (n *NameNode) String() string {\n\tif n.Index != nil {\n\t\treturn n.Ident + \"[\" + n.Index.String() + \"]\"\n\t}\n\n\treturn n.Ident\n}\n\nfunc (n *AssignNode) string() (string, bool) {\n\tvar (\n\t\tmulti bool\n\t)\n\n\tobjs := n.Values\n\tlhs := getlhs(n)\n\n\tret := \"\"\n\n\tfor i := 0; i < len(objs); i++ {\n\t\tvar (\n\t\t\tobjStr   string\n\t\t\tobjmulti bool\n\t\t)\n\n\t\tobj := objs[i]\n\n\t\tif obj.Type().IsExpr() {\n\t\t\tif obj.Type() == NodeListExpr {\n\t\t\t\tlobj := obj.(*ListExpr)\n\t\t\t\tobjStr, objmulti = lobj.string()\n\t\t\t} else {\n\t\t\t\tobjStr = obj.String()\n\t\t\t}\n\t\t}\n\n\t\tif i == 0 {\n\t\t\tif n.eqSpace > len(lhs) && !multi {\n\t\t\t\tret = lhs + strings.Repeat(\" \", n.eqSpace-len(lhs)) + \"= \" + objStr\n\t\t\t} else {\n\t\t\t\tret = lhs + \" = \" + objStr\n\t\t\t}\n\t\t} else if i < len(objs)-1 {\n\t\t\tret = ret + \", \" + objStr + \", \"\n\t\t} else {\n\t\t\tret = ret + \", \" + objStr\n\t\t}\n\n\t\tif objmulti && !multi {\n\t\t\tmulti = true\n\t\t}\n\t}\n\n\treturn ret, multi\n}\n\n// String returns the string representation of assignment statement\nfunc (n *AssignNode) String() string {\n\tstr, _ := n.string()\n\treturn str\n}\n\nfunc (n *ExecAssignNode) string() (string, bool) {\n\tvar (\n\t\tcmdStr string\n\t\tmulti  bool\n\t)\n\n\tlhs := getlhs(n)\n\n\tif n.cmd.Type() == NodeCommand {\n\t\tcmd := n.cmd.(*CommandNode)\n\t\tcmdStr, multi = cmd.string()\n\t} else if n.cmd.Type() == NodePipe {\n\t\tcmd := n.cmd.(*PipeNode)\n\t\tcmdStr, multi = cmd.string()\n\t} else {\n\t\tcmd := n.cmd.(*FnInvNode)\n\t\tcmdStr, multi = cmd.string()\n\t}\n\n\tif n.eqSpace > len(lhs) {\n\t\tret := lhs + strings.Repeat(\" \", n.eqSpace-len(lhs)) + \"<= \" + cmdStr\n\t\treturn ret, multi\n\t}\n\n\treturn lhs + \" <= \" + cmdStr, multi\n}\n\n// String returns the string representation of command assignment statement\nfunc (n *ExecAssignNode) String() string {\n\tstr, _ := n.string()\n\treturn str\n}\n\nfunc (n *CommandNode) toStringParts() ([]string, int) {\n\tvar (\n\t\tcontent  []string\n\t\tline     string\n\t\tlast     = len(n.args) - 1\n\t\ttotalLen = 0\n\t)\n\n\tfor i := 0; i < len(n.args); i += 2 {\n\t\tvar next string\n\n\t\targ := n.args[i].String()\n\n\t\tif i < last {\n\t\t\tnext = n.args[i+1].String()\n\t\t}\n\n\t\tif i == 0 {\n\t\t\targ = n.name + \" \" + arg\n\t\t}\n\n\t\tif arg[0] == '-' {\n\t\t\tif line != \"\" {\n\t\t\t\tcontent = append(content, line)\n\t\t\t\tline = \"\"\n\t\t\t}\n\n\t\t\tif len(next) > 0 && next[0] != '-' {\n\t\t\t\tif line == \"\" {\n\t\t\t\t\tline += arg + \" \" + next\n\t\t\t\t} else {\n\t\t\t\t\tline += \" \" + arg + \" \" + next\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcontent = append(content, arg, next)\n\t\t\t}\n\t\t} else if next != \"\" {\n\t\t\tif line == \"\" {\n\t\t\t\tline += arg + \" \" + next\n\t\t\t} else {\n\t\t\t\tline += \" \" + arg + \" \" + next\n\t\t\t}\n\t\t} else {\n\t\t\tif line == \"\" {\n\t\t\t\tline += arg\n\t\t\t} else {\n\t\t\t\tline += \" \" + arg\n\t\t\t}\n\t\t}\n\n\t\ttotalLen += len(arg) + len(next) + 1\n\n\t}\n\n\tif line != \"\" {\n\t\tcontent = append(content, line)\n\t}\n\n\tif len(content) == 0 {\n\t\tcontent = append(content, n.name)\n\t}\n\n\tfor i := 0; i < len(n.redirs); i++ {\n\t\trstr := n.redirs[i].String()\n\t\ttotalLen += len(rstr) + 1\n\t\tcontent = append(content, rstr)\n\t}\n\n\treturn content, totalLen\n}\n\nfunc (n *CommandNode) multiString() string {\n\tcontent, totalLen := n.toStringParts()\n\n\tif totalLen < 50 {\n\t\treturn \"(\" + strings.Join(content, \" \") + \")\"\n\t}\n\n\tcontent[0] = \"\\t\" + content[0]\n\n\tgentab := func(n int) string { return strings.Repeat(\"\\t\", n) }\n\ttabLen := (len(content[0]) + 7) / 8\n\n\tfor i := 1; i < len(content); i++ {\n\t\tcontent[i] = gentab(tabLen) + content[i]\n\t}\n\n\treturn \"(\\n\" + strings.Join(content, \"\\n\") + \"\\n)\"\n}\n\n// String returns the string representation of command statement\nfunc (n *CommandNode) string() (string, bool) {\n\tif n.multi {\n\t\treturn n.multiString(), true\n\t}\n\n\tvar content []string\n\n\tcontent = append(content, n.name)\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tcontent = append(content, n.args[i].String())\n\t}\n\n\tfor i := 0; i < len(n.redirs); i++ {\n\t\tcontent = append(content, n.redirs[i].String())\n\t}\n\n\treturn strings.Join(content, \" \"), false\n}\n\nfunc (n *CommandNode) String() string {\n\tstr, _ := n.string()\n\treturn str\n}\n\nfunc (n *PipeNode) multiString() string {\n\ttotalLen := 0\n\n\ttype cmdData struct {\n\t\tcontent  []string\n\t\ttotalLen int\n\t}\n\n\tcontent := make([]cmdData, len(n.cmds))\n\n\tfor i := 0; i < len(n.cmds); i++ {\n\t\tcmdContent, cmdLen := n.cmds[i].toStringParts()\n\n\t\tcontent[i] = cmdData{\n\t\t\tcmdContent,\n\t\t\tcmdLen,\n\t\t}\n\n\t\ttotalLen += cmdLen\n\t}\n\n\tif totalLen+3 < 50 {\n\t\tresult := \"(\"\n\n\t\tfor i := 0; i < len(content); i++ {\n\t\t\tresult += strings.Join(content[i].content, \" \")\n\n\t\t\tif i < len(content)-1 {\n\t\t\t\tresult += \" | \"\n\t\t\t}\n\t\t}\n\n\t\treturn result + \")\"\n\t}\n\n\tgentab := func(n int) string { return strings.Repeat(\"\\t\", n) }\n\n\tresult := \"(\\n\"\n\n\tfor i := 0; i < len(content); i++ {\n\t\tcmdContent := content[i].content\n\n\t\tcmdContent[0] = \"\\t\" + cmdContent[0]\n\t\ttabLen := (len(cmdContent[0]) + 7) / 8\n\n\t\tfor j := 1; j < len(cmdContent); j++ {\n\t\t\tcmdContent[j] = gentab(tabLen) + cmdContent[j]\n\t\t}\n\n\t\tresult += strings.Join(cmdContent, \"\\n\")\n\n\t\tif i < len(content)-1 {\n\t\t\tresult += \" |\\n\"\n\t\t}\n\t}\n\n\treturn result + \"\\n)\"\n}\n\n// String returns the string representation of pipeline statement\nfunc (n *PipeNode) string() (string, bool) {\n\tif n.multi {\n\t\treturn n.multiString(), true\n\t}\n\n\tret := \"\"\n\n\tfor i := 0; i < len(n.cmds); i++ {\n\t\tret += n.cmds[i].String()\n\n\t\tif i < (len(n.cmds) - 1) {\n\t\t\tret += \" | \"\n\t\t}\n\t}\n\n\treturn ret, false\n}\n\nfunc (n *PipeNode) String() string {\n\tstr, _ := n.string()\n\treturn str\n}\n\n// String returns the string representation of redirect\nfunc (r *RedirectNode) String() string {\n\tvar result string\n\n\tif r.rmap.lfd == r.rmap.rfd {\n\t\tif r.location != nil {\n\t\t\treturn \"> \" + r.location.String()\n\t\t}\n\n\t\treturn \"\"\n\t}\n\n\tif r.rmap.rfd >= 0 {\n\t\tresult = \">[\" + strconv.Itoa(r.rmap.lfd) + \"=\" + strconv.Itoa(r.rmap.rfd) + \"]\"\n\t} else if r.rmap.rfd == RedirMapNoValue {\n\t\tresult = \">[\" + strconv.Itoa(r.rmap.lfd) + \"]\"\n\t} else if r.rmap.rfd == RedirMapSupress {\n\t\tresult = \">[\" + strconv.Itoa(r.rmap.lfd) + \"=]\"\n\t}\n\n\tif r.location != nil {\n\t\tresult = result + \" \" + r.location.String()\n\t}\n\n\treturn result\n}\n\n// String returns the string representation of rfork statement\nfunc (n *RforkNode) String() string {\n\trforkstr := \"rfork \" + n.arg.String()\n\ttree := n.Tree()\n\n\tif tree != nil {\n\t\trforkstr += \" {\\n\"\n\t\tblock := tree.String()\n\t\tstmts := strings.Split(block, \"\\n\")\n\n\t\tfor i := 0; i < len(stmts); i++ {\n\t\t\tstmts[i] = \"\\t\" + stmts[i]\n\t\t}\n\n\t\trforkstr += strings.Join(stmts, \"\\n\") + \"\\n}\"\n\t}\n\n\treturn rforkstr\n}\n\n// String returns the string representation of comment\nfunc (n *CommentNode) String() string {\n\treturn n.val\n}\n\n// String returns the string representation of if statement\nfunc (n *IfNode) String() string {\n\tvar lstr, rstr string\n\n\tlstr = n.lvalue.String()\n\trstr = n.rvalue.String()\n\n\tifStr := \"if \" + lstr + \" \" + n.op + \" \" + rstr + \" {\\n\"\n\n\tifTree := n.IfTree()\n\n\tblock := ifTree.String()\n\tstmts := strings.Split(block, \"\\n\")\n\n\tif strings.TrimSpace(block) != \"\" {\n\t\tfor i := 0; i < len(stmts); i++ {\n\t\t\tstmts[i] = \"\\t\" + stmts[i]\n\t\t}\n\t}\n\n\tifStr += strings.Join(stmts, \"\\n\") + \"\\n}\"\n\n\telseTree := n.ElseTree()\n\n\tif elseTree != nil {\n\t\tifStr += \" else \"\n\n\t\telseBlock := elseTree.String()\n\t\telsestmts := strings.Split(elseBlock, \"\\n\")\n\n\t\tfor i := 0; i < len(elsestmts); i++ {\n\t\t\tif !n.IsElseIf() {\n\t\t\t\telsestmts[i] = \"\\t\" + elsestmts[i]\n\t\t\t}\n\t\t}\n\n\t\tif !n.IsElseIf() {\n\t\t\tifStr += \"{\\n\"\n\t\t}\n\n\t\tifStr += strings.Join(elsestmts, \"\\n\")\n\n\t\tif !n.IsElseIf() {\n\t\t\tifStr += \"\\n}\"\n\t\t}\n\t}\n\n\treturn ifStr\n}\n\nfunc (n *VarAssignDeclNode) String() string     { return \"var \" + n.Assign.String() }\nfunc (n *VarExecAssignDeclNode) String() string { return \"var \" + n.ExecAssign.String() }\n\n// String returns the string representation of function declaration\nfunc (n *FnDeclNode) String() string {\n\tfnStr := \"fn\"\n\n\tif n.name != \"\" {\n\t\tfnStr += \" \" + n.name + \"(\"\n\t}\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tfnStr += n.args[i].String()\n\t\tif i < (len(n.args) - 1) {\n\t\t\tfnStr += \", \"\n\t\t}\n\t}\n\n\tfnStr += \") {\\n\"\n\n\ttree := n.Tree()\n\n\tstmts := strings.Split(tree.String(), \"\\n\")\n\n\tfor i := 0; i < len(stmts); i++ {\n\t\tif len(stmts[i]) > 0 {\n\t\t\tfnStr += \"\\t\" + stmts[i] + \"\\n\"\n\t\t} else {\n\t\t\tfnStr += \"\\n\"\n\t\t}\n\t}\n\n\tfnStr += \"}\"\n\n\treturn fnStr\n}\n\nfunc (arg *FnArgNode) String() string {\n\tret := arg.Name\n\tif arg.IsVariadic {\n\t\tret += \"...\"\n\t}\n\treturn ret\n}\n\n// String returns the string representation of function invocation\nfunc (n *FnInvNode) string() (string, bool) {\n\tfnInvStr := n.name + \"(\"\n\n\tfor i := 0; i < len(n.args); i++ {\n\t\tfnInvStr += n.args[i].String()\n\n\t\tif i < (len(n.args) - 1) {\n\t\t\tfnInvStr += \", \"\n\t\t}\n\t}\n\n\tfnInvStr += \")\"\n\n\treturn fnInvStr, false\n}\n\nfunc (n *FnInvNode) String() string {\n\tstr, _ := n.string()\n\treturn str\n}\n\n// String returns the string representation of bindfn\nfunc (n *BindFnNode) String() string {\n\treturn \"bindfn \" + n.name + \" \" + n.cmdname\n}\n\n// String returns the string representation of return statement\nfunc (n *ReturnNode) String() string {\n\tvar returns []string\n\n\tret := \"return\"\n\n\treturnExprs := n.Returns\n\n\tfor i := 0; i < len(returnExprs); i++ {\n\t\treturns = append(returns, returnExprs[i].String())\n\t}\n\n\tif len(returns) > 0 {\n\t\treturn ret + \" \" + strings.Join(returns, \", \")\n\t}\n\n\treturn ret\n}\n\n// String returns the string representation of for statement\nfunc (n *ForNode) String() string {\n\tret := \"for\"\n\n\tif n.identifier != \"\" {\n\t\tret += \" \" + n.identifier + \" in \" + n.inExpr.String()\n\t}\n\n\tret += \" {\\n\"\n\n\ttree := n.Tree()\n\n\tstmts := strings.Split(tree.String(), \"\\n\")\n\n\tfor i := 0; i < len(stmts); i++ {\n\t\tif len(stmts[i]) > 0 {\n\t\t\tret += \"\\t\" + stmts[i] + \"\\n\"\n\t\t} else {\n\t\t\tret += \"\\n\"\n\t\t}\n\t}\n\n\tret += \"}\"\n\n\treturn ret\n}\n\nfunc stringify(s string) string {\n\tbuf := make([]byte, 0, len(s))\n\n\tfor i := 0; i < len(s); i++ {\n\t\tswitch s[i] {\n\t\tcase '\"':\n\t\t\tbuf = append(buf, '\\\\', '\"')\n\t\tcase '\\t':\n\t\t\tbuf = append(buf, '\\\\', 't')\n\t\tcase '\\n':\n\t\t\tbuf = append(buf, '\\\\', 'n')\n\t\tcase '\\r':\n\t\t\tbuf = append(buf, '\\\\', 'r')\n\t\tcase '\\\\':\n\t\t\tbuf = append(buf, '\\\\', '\\\\')\n\t\tdefault:\n\t\t\tbuf = append(buf, s[i])\n\t\t}\n\t}\n\n\treturn string(buf)\n}\n\nfunc getlhs(node assignable) string {\n\tvar nameStrs []string\n\n\tnodeNames := node.names()\n\n\tfor i := 0; i < len(nodeNames); i++ {\n\t\tnameStrs = append(nameStrs, nodeNames[i].String())\n\t}\n\n\treturn strings.Join(nameStrs, \", \")\n}\n"
  },
  {
    "path": "ast/node_fmt_test.go",
    "content": "package ast\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/token\"\n)\n\nfunc testPrinter(t *testing.T, node Node, expected string) {\n\tif node.String() != expected {\n\t\tt.Errorf(\"Values differ: '%s' != '%s'\", node, expected)\n\t}\n}\n\nfunc TestAstPrinterStringExpr(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\texpected string\n\t\tnode     Node\n\t}{\n\t\t// quote\n\t\t{\n\t\t\texpected: `\"\\\"\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\\"\", true),\n\t\t},\n\n\t\t// escape\n\t\t{\n\t\t\texpected: `\"\\\\this is a test\\n\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\\\this is a test\\n\", true),\n\t\t},\n\n\t\t// tab\n\t\t{\n\t\t\texpected: `\"this is a test\\t\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"this is a test\\t\", true),\n\t\t},\n\n\t\t// linefeed\n\t\t{\n\t\t\texpected: `\"this is a test\\n\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"this is a test\\n\", true),\n\t\t},\n\t\t{\n\t\t\texpected: `\"\\nthis is a test\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\nthis is a test\", true),\n\t\t},\n\t\t{\n\t\t\texpected: `\"\\n\\n\\n\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\n\\n\\n\", true),\n\t\t},\n\n\t\t// carriege return\n\t\t{\n\t\t\texpected: `\"this is a test\\r\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"this is a test\\r\", true),\n\t\t},\n\t\t{\n\t\t\texpected: `\"\\rthis is a test\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\rthis is a test\", true),\n\t\t},\n\t\t{\n\t\t\texpected: `\"\\r\\r\\r\"`,\n\t\t\tnode:     NewStringExpr(token.NewFileInfo(1, 0), \"\\r\\r\\r\", true),\n\t\t},\n\t} {\n\t\ttestPrinter(t, testcase.node, testcase.expected)\n\t}\n}\n\nfunc TestASTPrinterAssignment(t *testing.T) {\n\tzeroFileInfo := token.NewFileInfo(1, 0)\n\n\tfor _, testcase := range []struct {\n\t\texpected string\n\t\tnode     Node\n\t}{\n\t\t{\n\t\t\texpected: `a = \"1\"`,\n\t\t\tnode: NewAssignNode(zeroFileInfo, []*NameNode{\n\t\t\t\tNewNameNode(zeroFileInfo, \"a\", nil),\n\t\t\t}, []Expr{NewStringExpr(zeroFileInfo, \"1\", true)}),\n\t\t},\n\t\t{\n\t\t\texpected: `a = ()`,\n\t\t\tnode: NewAssignNode(zeroFileInfo, []*NameNode{\n\t\t\t\tNewNameNode(zeroFileInfo, \"a\", nil),\n\t\t\t}, []Expr{NewListExpr(zeroFileInfo, []Expr{})}),\n\t\t},\n\t\t{\n\t\t\texpected: `a, b = (), ()`,\n\t\t\tnode: NewAssignNode(zeroFileInfo, []*NameNode{\n\t\t\t\tNewNameNode(zeroFileInfo, \"a\", nil),\n\t\t\t\tNewNameNode(zeroFileInfo, \"b\", nil),\n\t\t\t}, []Expr{NewListExpr(zeroFileInfo, []Expr{}),\n\t\t\t\tNewListExpr(zeroFileInfo, []Expr{})}),\n\t\t},\n\t\t{\n\t\t\texpected: `a, b = \"1\", \"2\"`,\n\t\t\tnode: NewAssignNode(zeroFileInfo, []*NameNode{\n\t\t\t\tNewNameNode(zeroFileInfo, \"a\", nil),\n\t\t\t\tNewNameNode(zeroFileInfo, \"b\", nil),\n\t\t\t}, []Expr{NewStringExpr(zeroFileInfo, \"1\", true),\n\t\t\t\tNewStringExpr(zeroFileInfo, \"2\", true)}),\n\t\t},\n\t} {\n\t\ttestPrinter(t, testcase.node, testcase.expected)\n\t}\n}\n"
  },
  {
    "path": "ast/nodetype_string.go",
    "content": "// Code generated by \"stringer -type=NodeType\"; DO NOT EDIT\n\npackage ast\n\nimport \"fmt\"\n\nconst _NodeType_name = \"NodeSetenvNodeBlockNodeNameNodeAssignNodeExecAssignNodeImportexecBeginNodeCommandNodePipeNodeRedirectNodeFnInvexecEndexpressionBeginNodeStringExprNodeIntExprNodeVarExprNodeListExprNodeIndexExprNodeConcatExprexpressionEndNodeStringNodeRforkNodeRforkFlagsNodeIfNodeCommentNodeFnArgNodeVarAssignDeclNodeVarExecAssignDeclNodeFnDeclNodeReturnNodeBindFnNodeDumpNodeFor\"\n\nvar _NodeType_index = [...]uint16{0, 10, 19, 27, 37, 51, 61, 70, 81, 89, 101, 110, 117, 132, 146, 157, 168, 180, 193, 207, 220, 230, 239, 253, 259, 270, 279, 296, 317, 327, 337, 347, 355, 362}\n\nfunc (i NodeType) String() string {\n\ti -= 1\n\tif i < 0 || i >= NodeType(len(_NodeType_index)-1) {\n\t\treturn fmt.Sprintf(\"NodeType(%d)\", i+1)\n\t}\n\treturn _NodeType_name[_NodeType_index[i]:_NodeType_index[i+1]]\n}\n"
  },
  {
    "path": "ast/tree.go",
    "content": "package ast\n\ntype (\n\t// Tree is the AST\n\tTree struct {\n\t\tName string\n\t\tRoot *BlockNode // top-level root of the tree.\n\t}\n)\n\n// NewTree creates a new AST tree\nfunc NewTree(name string) *Tree {\n\treturn &Tree{\n\t\tName: name,\n\t}\n}\n\nfunc (t *Tree) IsEqual(other *Tree) bool {\n\tif t == other {\n\t\treturn true\n\t}\n\n\treturn t.Root.IsEqual(other.Root)\n}\n\nfunc (tree *Tree) String() string {\n\tif tree.Root == nil {\n\t\treturn \"\"\n\t}\n\n\tif len(tree.Root.Nodes) == 0 {\n\t\treturn \"\"\n\t}\n\n\treturn tree.Root.String()\n}\n"
  },
  {
    "path": "ast/tree_test.go",
    "content": "package ast\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/token\"\n)\n\n// Test API\nfunc TestTreeCreation(t *testing.T) {\n\ttr := NewTree(\"example\")\n\n\tif tr.Name != \"example\" {\n\t\tt.Errorf(\"Invalid name\")\n\t\treturn\n\t}\n}\n\nfunc TestTreeRawCreation(t *testing.T) {\n\ttr := NewTree(\"creating a tree by hand\")\n\n\tln := NewBlockNode(token.NewFileInfo(1, 0))\n\trfarg := NewStringExpr(token.NewFileInfo(1, 0), \"unp\", false)\n\n\tr := NewRforkNode(token.NewFileInfo(1, 0))\n\tr.SetFlags(rfarg)\n\tln.Push(r)\n\n\ttr.Root = ln\n\n\tif tr.String() != \"rfork unp\" {\n\t\tt.Error(\"Failed to build AST by hand\")\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/cli.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/madlambda/nash\"\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/parser\"\n\t\"github.com/madlambda/nash/sh\"\n\t\"github.com/chzyer/readline\"\n)\n\ntype (\n\tInterrupted interface {\n\t\tInterrupted() bool\n\t}\n\n\tIgnored interface {\n\t\tIgnore() bool\n\t}\n\n\tBlockNotFinished interface {\n\t\tUnfinished() bool\n\t}\n)\n\nvar completers = []readline.PrefixCompleterInterface{}\n\nfunc execFn(shell *nash.Shell, fnDef sh.FnDef, args []sh.Obj) {\n\tfn := fnDef.Build()\n\terr := fn.SetArgs(args)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s failed: %s\\n\", fnDef.Name(), err.Error())\n\t}\n\tfn.SetStdin(shell.Stdin())\n\tfn.SetStdout(shell.Stdout())\n\tfn.SetStderr(shell.Stderr())\n\n\tif err := fn.Start(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s failed: %s\\n\", fnDef.Name(), err.Error())\n\t\treturn\n\t}\n\n\tif err := fn.Wait(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s failed: %s\\n\", fnDef.Name(), err.Error())\n\t\treturn\n\t}\n}\n\nfunc importInitFile(shell *nash.Shell, initFile string) (bool, error) {\n\tif d, err := os.Stat(initFile); err == nil {\n\t\tif m := d.Mode(); !m.IsDir() {\n\t\t\terr := shell.ExecuteString(\"init\",\n\t\t\t\tfmt.Sprintf(\"import %q\", initFile))\n\t\t\tif err != nil {\n\t\t\t\treturn false, fmt.Errorf(\"Failed to evaluate '%s': %s\", initFile, err.Error())\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc loadInit(shell *nash.Shell) error {\n\t\n\tif noInit {\n\t\treturn nil\n\t}\n\n\tinitFiles := []string{\n\t\tshell.NashPath() + \"/init\",\n\t\tshell.NashPath() + \"/init.sh\",\n\t}\n\n\tfor _, init := range initFiles {\n\t\timported, err := importInitFile(shell, init)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif imported {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc cli(shell *nash.Shell) error {\n\n\tshell.SetInteractive(true)\n\n\tif err := loadInit(shell); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error loading init file:\\n%s\\n\", err)\n\t}\n\n\thistoryFile := shell.NashPath() + \"/history\"\n\tcfg := readline.Config{\n\t\tPrompt:          shell.Prompt(),\n\t\tHistoryFile:     historyFile,\n\t\tInterruptPrompt: \"^C\",\n\t\tEOFPrompt:       \"exit\",\n\t}\n\n\tterm, err := readline.NewTerminal(&cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\top := term.Readline()\n\trline := &readline.Instance{\n\t\tConfig:    &cfg,\n\t\tTerminal:  term,\n\t\tOperation: op,\n\t}\n\n\tdefer rline.Close()\n\n\tcompleter := NewCompleter(op, term, shell)\n\tcfg.AutoComplete = completer\n\n\tif lineMode, ok := shell.Getvar(\"LINEMODE\"); ok {\n\t\tif lineStr, ok := lineMode.(*sh.StrObj); ok && lineStr.Str() == \"vim\" {\n\t\t\trline.SetVimMode(true)\n\t\t} else {\n\t\t\trline.SetVimMode(false)\n\t\t}\n\t}\n\n\treturn docli(shell, rline)\n}\n\nfunc docli(shell *nash.Shell, rline *readline.Instance) error {\n\tvar (\n\t\tcontent    bytes.Buffer\n\t\tlineidx    int\n\t\tline       string\n\t\tparse      *parser.Parser\n\t\ttr         *ast.Tree\n\t\terr        error\n\t\tunfinished bool\n\t\tprompt     string\n\t)\n\n\tfor {\n\t\tif fnDef, err := shell.GetFn(\"nash_repl_before\"); err == nil && !unfinished {\n\t\t\texecFn(shell, fnDef, nil)\n\t\t}\n\n\t\tif !unfinished {\n\t\t\tprompt = shell.Prompt()\n\t\t}\n\n\t\trline.SetPrompt(prompt)\n\t\tline, err = rline.Readline()\n\n\t\tif err == readline.ErrInterrupt {\n\t\t\tgoto cont\n\t\t} else if err == io.EOF {\n\t\t\terr = nil\n\t\t\tbreak\n\t\t}\n\n\t\tlineidx++\n\t\tline = strings.TrimSpace(line)\n\n\t\t// handle special cli commands\n\t\tswitch {\n\t\tcase strings.HasPrefix(line, \"set mode \"):\n\t\t\tswitch line[9:] {\n\t\t\tcase \"vi\":\n\t\t\t\trline.SetVimMode(true)\n\t\t\tcase \"emacs\":\n\t\t\t\trline.SetVimMode(false)\n\t\t\tdefault:\n\t\t\t\tfmt.Printf(\"invalid mode: %s\\n\", line[9:])\n\t\t\t}\n\n\t\t\tgoto cont\n\t\tcase line == \"mode\":\n\t\t\tif rline.IsVimMode() {\n\t\t\t\tfmt.Printf(\"Current mode: vim\\n\")\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\"Current mode: emacs\\n\")\n\t\t\t}\n\n\t\t\tgoto cont\n\t\tcase line == \"exit\":\n\t\t\tbreak\n\t\t}\n\n\t\tcontent.Write([]byte(line + \"\\n\"))\n\t\tparse = parser.NewParser(fmt.Sprintf(\"<stdin line %d>\", lineidx), string(content.Bytes()))\n\t\tline = string(content.Bytes())\n\n\t\ttr, err = parse.Parse()\n\t\tif err != nil {\n\t\t\tif interrupted, ok := err.(Interrupted); ok && interrupted.Interrupted() {\n\t\t\t\tcontent.Reset()\n\t\t\t\tgoto cont\n\t\t\t} else if errBlock, ok := err.(BlockNotFinished); ok && errBlock.Unfinished() {\n\t\t\t\tprompt = \">>> \"\n\t\t\t\tunfinished = true\n\t\t\t\tgoto cont\n\t\t\t}\n\n\t\t\tfmt.Printf(\"ERROR: %s\\n\", err.Error())\n\t\t\tcontent.Reset()\n\t\t\tgoto cont\n\t\t}\n\n\t\tunfinished = false\n\t\tcontent.Reset()\n\n\t\t_, err = shell.ExecuteTree(tr)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"ERROR: %s\\n\", err.Error())\n\t\t}\n\n\tcont:\n\t\tif fnDef, err := shell.GetFn(\"nash_repl_after\"); err == nil && !unfinished {\n\t\t\tvar status sh.Obj\n\t\t\tvar ok bool\n\n\t\t\tif status, ok = shell.Getvar(\"status\"); !ok {\n\t\t\t\tstatus = sh.NewStrObj(\"\")\n\t\t\t}\n\n\t\t\texecFn(shell, fnDef, []sh.Obj{sh.NewStrObj(line), status})\n\t\t}\n\n\t\trline.SetPrompt(prompt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nash/completer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/madlambda/nash\"\n\t\"github.com/madlambda/nash/sh\"\n\t\"github.com/chzyer/readline\"\n)\n\nvar runes = readline.Runes{}\n\ntype Completer struct {\n\top   *readline.Operation\n\tterm *readline.Terminal\n\tsh   *nash.Shell\n}\n\nfunc NewCompleter(op *readline.Operation, term *readline.Terminal, sh *nash.Shell) *Completer {\n\treturn &Completer{op, term, sh}\n}\n\nfunc (c *Completer) Do(line []rune, pos int) ([][]rune, int) {\n\tvar (\n\t\tnewLine [][]rune\n\t\toffset  int\n\t\tlineArg = sh.NewStrObj(string(line))\n\t\tposArg  = sh.NewStrObj(strconv.Itoa(pos))\n\t)\n\n\tdefer c.op.Refresh()\n\tdefer c.term.PauseRead(false)\n\n\tfnDef, err := c.sh.GetFn(\"nash_complete\")\n\tif err != nil {\n\t\t// no complete available\n\t\treturn [][]rune{[]rune{'\\t'}}, offset\n\t}\n\n\tnashFunc := fnDef.Build()\n\terr = nashFunc.SetArgs([]sh.Obj{lineArg, posArg})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to autocomplete: %s\\n\", err.Error())\n\t\treturn newLine, offset\n\t}\n\n\tnashFunc.SetStdin(c.sh.Stdin())\n\tnashFunc.SetStdout(c.sh.Stdout())\n\tnashFunc.SetStderr(c.sh.Stderr())\n\n\tif err = nashFunc.Start(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to autocomplete: %s\\n\", err.Error())\n\t\treturn newLine, offset\n\t}\n\n\tif err = nashFunc.Wait(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to autocomplete: %s\\n\", err.Error())\n\t\treturn newLine, offset\n\t}\n\n\tret := nashFunc.Results()\n\n\tif len(ret) != 1 || ret[0].Type() != sh.ListType {\n\t\tfmt.Fprintf(os.Stderr, \"ignoring autocomplete value: %v\\n\", ret)\n\t\treturn newLine, offset\n\t}\n\n\tretval := ret[0]\n\n\tretlist := retval.(*sh.ListObj)\n\n\tif len(retlist.List()) != 2 {\n\t\treturn newLine, pos\n\t}\n\n\tnewline := retlist.List()[0]\n\tnewpos := retlist.List()[1]\n\n\tif newline.Type() != sh.StringType || newpos.Type() != sh.StringType {\n\t\tfmt.Fprintf(os.Stderr, \"ignoring autocomplete value: (%s) (%s)\\n\", newline, newpos)\n\t\treturn newLine, offset\n\t}\n\n\tobjline := newline.(*sh.StrObj)\n\tobjpos := newpos.(*sh.StrObj)\n\n\tnewoffset, err := strconv.Atoi(objpos.Str())\n\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to autocomplete: %s\\n\", err.Error())\n\t\treturn newLine, offset\n\t}\n\n\tnewLine = append(newLine, []rune(objline.Str()))\n\n\treturn newLine, newoffset\n}\n"
  },
  {
    "path": "cmd/nash/env.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc NashPath() (string, error) {\n\tnashpath := os.Getenv(\"NASHPATH\")\n\tif nashpath != \"\" {\n\t\treturn nashpath, nil\n\t}\n\th, err := home()\n\treturn filepath.Join(h, \"nash\"), err\n}\n\nfunc NashRoot() (string, error) {\n\tnashroot, ok := os.LookupEnv(\"NASHROOT\")\n\tif ok {\n\t\treturn nashroot, nil\n\t}\n\n\th, err := home()\n\treturn filepath.Join(h, \"nashroot\"), err\n}\n\nfunc home() (string, error) {\n\thomedir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif homedir == \"\" {\n\t\treturn \"\", errors.New(\"invalid empty home dir\")\n\t}\n\treturn homedir, nil\n}\n"
  },
  {
    "path": "cmd/nash/env_test.go",
    "content": "package main_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\tmain \"github.com/madlambda/nash/cmd/nash\"\n)\n\n// TODO: No idea on how to inject failures like empty HOME folders for now\n\nfunc TestLoadNASHPATH(t *testing.T) {\n\n\tdefaultNashPath := filepath.Join(home(t), \"nash\")\n\n\trunTests(t, main.NashPath, []EnvTest{\n\t\t{\n\t\t\tname: \"Exported\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"NASHPATH\": filepath.Join(\"etc\", \"nash\"),\n\t\t\t},\n\t\t\twant: filepath.Join(\"etc\", \"nash\"),\n\t\t},\n\t\t{\n\t\t\tname: \"IgnoresNASHROOT\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"NASHROOT\": \"/etc/nashroot/tests\",\n\t\t\t\t\"HOME\":     home(t),\n\t\t\t},\n\t\t\twant: defaultNashPath,\n\t\t},\n\t\t{\n\t\t\tname: \"UseUserHomeWhenUnset\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"NASHROOT\": \"/etc/nashroot/tests\",\n\t\t\t\t\"HOME\":     home(t),\n\t\t\t},\n\t\t\twant: defaultNashPath,\n\t\t},\n\t})\n}\n\nfunc TestLoadNASHROOT(t *testing.T) {\n\tdefaultNashRoot := filepath.Join(home(t), \"nashroot\")\n\trunTests(t, main.NashRoot, []EnvTest{\n\t\t{\n\t\t\tname: \"Exported\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"NASHROOT\": filepath.Join(\"etc\", \"nashroot\"),\n\t\t\t},\n\t\t\twant: filepath.Join(\"etc\", \"nashroot\"),\n\t\t},\n\t\t{\n\t\t\tname: \"IgnoresGOPATHIfSet\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"GOPATH\":   filepath.Join(\"go\", \"natel\", \"review\"),\n\t\t\t\t\"NASHROOT\": filepath.Join(\"nashroot\", \"ignoredgopath\"),\n\t\t\t},\n\t\t\twant: filepath.Join(\"nashroot\", \"ignoredgopath\"),\n\t\t},\n\t\t{\n\t\t\tname: \"UsesHOMEevenWhenGOPATHIsSet\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"HOME\":   home(t),\n\t\t\t\t\"GOPATH\": filepath.Join(\"go\", \"path\"),\n\t\t\t},\n\t\t\twant: defaultNashRoot,\n\t\t},\n\t\t{\n\t\t\tname: \"UsesUserHomeWhenNASHROOTAndGOPATHAreUnset\",\n\t\t\tenv: map[string]string{\n\t\t\t\t\"HOME\": home(t),\n\t\t\t},\n\t\t\twant: filepath.Join(home(t), \"nashroot\"),\n\t\t},\n\t})\n}\n\nfunc runTests(t *testing.T, testfunc func() (string, error), cases []EnvTest) {\n\n\tt.Helper()\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\trestore := clearenv(t)\n\t\t\tdefer restore()\n\n\t\t\texport(t, c.env)\n\t\t\tgot, err := testfunc()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif got != c.want {\n\t\t\t\tt.Fatalf(\"got[%s] != want[%s]\", got, c.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype EnvTest struct {\n\tname string\n\tenv  map[string]string\n\twant string\n}\n\nfunc clearenv(t *testing.T) func() {\n\tenv := os.Environ()\n\tos.Clearenv()\n\n\treturn func() {\n\t\tfor _, envvar := range env {\n\t\t\tparsed := strings.Split(envvar, \"=\")\n\t\t\tname := parsed[0]\n\t\t\tval := strings.Join(parsed[1:], \"=\")\n\n\t\t\terr := os.Setenv(name, val)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"error[%s] restoring env var[%s]\", err, envvar)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc export(t *testing.T, env map[string]string) {\n\tt.Helper()\n\n\tfor name, val := range env {\n\t\terr := os.Setenv(name, val)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc home(t *testing.T) string {\n\tt.Helper()\n\n\thomedir, err := os.UserHomeDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn homedir\n}\n"
  },
  {
    "path": "cmd/nash/example.sh",
    "content": "#!/usr/bin/env nash\n\n-rm -rf rootfs\n\nrfork upmis {\n    mount -t proc proc /proc\n    mkdir -p rootfs\n    mount -t tmpfs -o size=1G tmpfs rootfs\n\n    cd rootfs\n\n    wget \"https://busybox.net/downloads/binaries/latest/busybox-x86_64\" -O busybox\n    chmod +x busybox\n\n    mkdir bin\n\n    ./busybox --install ./bin\n\n    mkdir -p proc\n    mkdir -p dev\n    mount -t proc proc proc\n    mount -t tmpfs tmpfs dev\n\n    cp ../nash .\n    chroot . /bin/sh\n}\n"
  },
  {
    "path": "cmd/nash/install.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc NashLibDir(nashpath string) string {\n\t//FIXME: This is sadly duplicated from the shell implementation =(\n\treturn filepath.Join(nashpath, \"lib\")\n}\n\nfunc InstallLib(nashpath string, sourcepath string) error {\n\tnashlibdir := NashLibDir(nashpath)\n\tsourcepathAbs, err := filepath.Abs(sourcepath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error[%s] getting absolute path of [%s]\", err, sourcepath)\n\t}\n\tif filepath.HasPrefix(sourcepathAbs, nashlibdir) {\n\t\treturn fmt.Errorf(\n\t\t\t\"lib source path[%s] can't be inside nash lib dir[%s]\", sourcepath, nashlibdir)\n\t}\n\treturn installLib(nashlibdir, sourcepathAbs)\n}\n\nfunc installLib(targetdir string, sourcepath string) error {\n\tf, err := os.Stat(sourcepath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error[%s] checking if path[%s] is dir\", err, sourcepath)\n\t}\n\n\tif !f.IsDir() {\n\t\treturn copyfile(targetdir, sourcepath)\n\t}\n\n\tbasedir := filepath.Base(sourcepath)\n\ttargetdir = filepath.Join(targetdir, basedir)\n\n\tfiles, err := ioutil.ReadDir(sourcepath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error[%s] reading dir[%s]\", err, sourcepath)\n\t}\n\n\tfor _, file := range files {\n\t\terr := installLib(targetdir, filepath.Join(sourcepath, file.Name()))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc copyfile(targetdir string, sourcefilepath string) error {\n\tfail := func(err error) error {\n\t\treturn fmt.Errorf(\n\t\t\t\"error[%s] trying to copy file[%s] to [%s]\", err, sourcefilepath, targetdir)\n\t}\n\n\terr := os.MkdirAll(targetdir, os.ModePerm)\n\tif err != nil {\n\t\treturn fail(err)\n\t}\n\n\tsourcefile, err := os.Open(sourcefilepath)\n\tif err != nil {\n\t\treturn fail(err)\n\t}\n\tdefer sourcefile.Close()\n\n\ttargetfilepath := filepath.Join(targetdir, filepath.Base(sourcefilepath))\n\ttargetfile, err := os.Create(targetfilepath)\n\tif err != nil {\n\t\treturn fail(err)\n\t}\n\tdefer targetfile.Close()\n\n\t_, err = io.Copy(targetfile, sourcefile)\n\treturn err\n}\n"
  },
  {
    "path": "cmd/nash/install_test.go",
    "content": "package main_test\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tmain \"github.com/madlambda/nash/cmd/nash\"\n\t\"github.com/madlambda/nash/internal/testing/fixture\"\n)\n\n// TODO: test when nashpath lib already exists and has libraries inside\n\nfunc TestInstallLib(t *testing.T) {\n\ttype testcase struct {\n\t\tname        string\n\t\tlibfiles    []string\n\t\tinstallpath string\n\t\t// want will map the wanted files to the original files copied from the lib\n\t\t// the wanted files paths are relative to inside the nashpath lib dir.\n\t\t// the files need to be mapped to the original files because of content validation\n\t\t// when multiple files are installed.\n\t\twant map[string]string\n\t}\n\n\tcases := []testcase{\n\t\t{\n\t\t\tname: \"SingleFile\",\n\t\t\tlibfiles: []string{\n\t\t\t\t\"/testfile/file.sh\",\n\t\t\t},\n\t\t\tinstallpath: \"/testfile/file.sh\",\n\t\t\twant: map[string]string{\n\t\t\t\t\"file.sh\": \"/testfile/file.sh\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"SingleDir\",\n\t\t\tlibfiles: []string{\n\t\t\t\t\"/testfile/file.sh\",\n\t\t\t},\n\t\t\tinstallpath: \"/testfile\",\n\t\t\twant: map[string]string{\n\t\t\t\t\"/testfile/file.sh\": \"/testfile/file.sh\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"SingleDirWithMultipleFiles\",\n\t\t\tlibfiles: []string{\n\t\t\t\t\"/testfile/file.sh\",\n\t\t\t\t\"/testfile/fileagain.sh\",\n\t\t\t},\n\t\t\tinstallpath: \"/testfile\",\n\t\t\twant: map[string]string{\n\t\t\t\t\"/testfile/file.sh\":      \"/testfile/file.sh\",\n\t\t\t\t\"/testfile/fileagain.sh\": \"/testfile/fileagain.sh\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"MultipleDirsWithMultipleFiles\",\n\t\t\tlibfiles: []string{\n\t\t\t\t\"/testfile/file.sh\",\n\t\t\t\t\"/testfile/dir1/file.sh\",\n\t\t\t\t\"/testfile/dir1/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/file.sh\",\n\t\t\t\t\"/testfile/dir2/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/dir3/file.sh\",\n\t\t\t},\n\t\t\tinstallpath: \"/testfile\",\n\t\t\twant: map[string]string{\n\t\t\t\t\"/testfile/file.sh\":           \"/testfile/file.sh\",\n\t\t\t\t\"/testfile/dir1/file.sh\":      \"/testfile/dir1/file.sh\",\n\t\t\t\t\"/testfile/dir1/fileagain.sh\": \"/testfile/dir1/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/file.sh\":      \"/testfile/dir2/file.sh\",\n\t\t\t\t\"/testfile/dir2/fileagain.sh\": \"/testfile/dir2/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/dir3/file.sh\": \"/testfile/dir2/dir3/file.sh\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"InstallOnlyFilesIndicatedByInstallDir\",\n\t\t\tlibfiles: []string{\n\t\t\t\t\"/testfile/file.sh\",\n\t\t\t\t\"/testfile/dir1/file.sh\",\n\t\t\t\t\"/testfile/dir1/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/file.sh\",\n\t\t\t\t\"/testfile/dir2/fileagain.sh\",\n\t\t\t\t\"/testfile/dir2/dir3/file.sh\",\n\t\t\t},\n\t\t\tinstallpath: \"/testfile/dir2\",\n\t\t\twant: map[string]string{\n\t\t\t\t\"/dir2/file.sh\":      \"/testfile/dir2/file.sh\",\n\t\t\t\t\"/dir2/fileagain.sh\": \"/testfile/dir2/fileagain.sh\",\n\t\t\t\t\"/dir2/dir3/file.sh\": \"/testfile/dir2/dir3/file.sh\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\t\t\tdefer rmnashpath()\n\n\t\t\tlibfilesDir, rmlibfilesDir := fixture.Tmpdir(t)\n\t\t\tdefer rmlibfilesDir()\n\n\t\t\tnashlibdir := main.NashLibDir(nashpath)\n\t\t\tlibfiles := []string{}\n\n\t\t\tlibfileFullPath := func(libfilepath string) string {\n\t\t\t\treturn filepath.Join(libfilesDir, libfilepath)\n\t\t\t}\n\n\t\t\tfor _, f := range c.libfiles {\n\t\t\t\tlibfiles = append(libfiles, libfileFullPath(f))\n\t\t\t}\n\n\t\t\tcreatedLibFiles := fixture.CreateFiles(t, libfiles)\n\t\t\tinstallpath := filepath.Join(libfilesDir, c.installpath)\n\n\t\t\terr := main.InstallLib(nashpath, installpath)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tlistNashPathFiles := func() []string {\n\t\t\t\tfiles := []string{}\n\t\t\t\tfilepath.Walk(nashpath, func(path string, stats os.FileInfo, err error) error {\n\t\t\t\t\tif stats.IsDir() {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tfiles = append(files, path)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\treturn files\n\t\t\t}\n\n\t\t\tgotFiles := listNashPathFiles()\n\n\t\t\tfatal := func() {\n\t\t\t\tt.Errorf(\"nashpath: [%s]\", nashpath)\n\t\t\t\tt.Errorf(\"nashpath contents:\")\n\n\t\t\t\tfor _, path := range gotFiles {\n\t\t\t\t\tt.Errorf(\"[%s]\", path)\n\t\t\t\t}\n\t\t\t\tt.Fatal(\"\")\n\t\t\t}\n\n\t\t\tif len(gotFiles) != len(c.want) {\n\t\t\t\tt.Errorf(\"wanted[%d] files but got[%d]\", len(c.want), len(gotFiles))\n\t\t\t\tfatal()\n\t\t\t}\n\n\t\t\tfor wantFilepath, libfilepath := range c.want {\n\t\t\t\tcompleteLibFilepath := libfileFullPath(libfilepath)\n\t\t\t\twantContents, ok := createdLibFiles[completeLibFilepath]\n\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"unable to find libfilepath[%s] contents on created lib files map[%+v]\", completeLibFilepath, createdLibFiles)\n\t\t\t\t\tt.Fatal(\"this probably means a wrongly specified test case with wanted files that are not present on the libfiles\")\n\t\t\t\t}\n\n\t\t\t\tfullWantFilepath := filepath.Join(nashlibdir, wantFilepath)\n\t\t\t\twantFile, err := os.Open(fullWantFilepath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"error[%s] checking wanted file[%s]\", err, wantFilepath)\n\t\t\t\t\tfatal()\n\t\t\t\t}\n\t\t\t\tgotContentsRaw, err := ioutil.ReadAll(wantFile)\n\t\t\t\twantFile.Close()\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"error[%s] checking existence of wanted file[%s]\", err, wantFilepath)\n\t\t\t\t\tfatal()\n\t\t\t\t}\n\n\t\t\t\tgotContents := string(gotContentsRaw)\n\t\t\t\tif gotContents != wantContents {\n\t\t\t\t\tt.Errorf(\"for file [%s] wanted contents [%s] but got [%s]\", wantFilepath, wantContents, gotContents)\n\t\t\t\t\tfatal()\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSourcePathCantBeEqualToNashLibDir(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tnashlibdir := main.NashLibDir(nashpath)\n\n\tfixture.CreateFile(t, filepath.Join(nashlibdir, \"whatever.sh\"))\n\n\tassertInstallLibFails(t, nashpath, nashlibdir)\n}\n\nfunc TestSourcePathCantBeInsideNashLibDir(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tnashlibdir := main.NashLibDir(nashpath)\n\tsourcelibdir := filepath.Join(nashlibdir, \"somedir\")\n\tfixture.CreateFile(t, filepath.Join(sourcelibdir, \"whatever.sh\"))\n\n\tassertInstallLibFails(t, nashpath, sourcelibdir)\n}\n\nfunc TestRelativeSourcePathCantBeInsideNashLibDir(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tnashlibdir := main.NashLibDir(nashpath)\n\tfixture.CreateFile(t, filepath.Join(nashlibdir, \"somedir\", \"whatever.sh\"))\n\n\toldwd := fixture.WorkingDir(t)\n\tdefer fixture.ChangeDir(t, oldwd)\n\n\tfixture.ChangeDir(t, nashlibdir)\n\n\tassertInstallLibFails(t, nashpath, \"./somedir\")\n}\n\nfunc TestFailsOnUnexistentSourcePath(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tassertInstallLibFails(t, nashpath, \"/nonexistent/nash/crap\")\n}\n\nfunc TestFailsOnUnreadableSourcePath(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tsourcedir, rmsourcedir := fixture.Tmpdir(t)\n\tdefer rmsourcedir()\n\n\tfixture.Chmod(t, sourcedir, writeOnly)\n\tassertInstallLibFails(t, nashpath, sourcedir)\n}\n\nfunc TestFailsOnUnreadableFileInsideSourcePath(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tsourcedir, rmsourcedir := fixture.Tmpdir(t)\n\tdefer rmsourcedir()\n\n\treadableFile := filepath.Join(sourcedir, \"file1.sh\")\n\tunreadableFile := filepath.Join(sourcedir, \"file2.sh\")\n\n\tfixture.CreateFiles(t, []string{readableFile, unreadableFile})\n\tfixture.Chmod(t, unreadableFile, writeOnly)\n\n\tassertInstallLibFails(t, nashpath, sourcedir)\n}\n\nfunc TestFailsOnUnwriteableNashPath(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tsourcedir, rmsourcedir := fixture.Tmpdir(t)\n\tdefer rmsourcedir()\n\n\tfixture.Chmod(t, nashpath, readOnly)\n\tfixture.CreateFile(t, filepath.Join(sourcedir, \"file.sh\"))\n\n\tassertInstallLibFails(t, nashpath, sourcedir)\n}\n\nfunc TestFailsOnUnwriteableFileInsideNashLibdir(t *testing.T) {\n\tnashpath, rmnashpath := fixture.Tmpdir(t)\n\tdefer rmnashpath()\n\n\tsourcedir, rmsourcedir := fixture.Tmpdir(t)\n\tdefer rmsourcedir()\n\n\tfilename := \"test.sh\"\n\tsourcefile := filepath.Join(sourcedir, filename)\n\texpectedInstalledFile := filepath.Join(\n\t\tmain.NashLibDir(nashpath),\n\t\tfilepath.Base(sourcedir),\n\t\tfilename,\n\t)\n\n\tfixture.CreateFiles(t, []string{sourcefile, expectedInstalledFile})\n\tfixture.Chmod(t, expectedInstalledFile, readOnly)\n\n\tassertInstallLibFails(t, nashpath, sourcedir)\n}\n\nfunc assertInstallLibFails(t *testing.T, nashpath string, sourcepath string) {\n\tt.Helper()\n\n\terr := main.InstallLib(nashpath, sourcepath)\n\tif err == nil {\n\t\tt.Fatal(\"expected error, got nil\")\n\t}\n}\n\nconst writeOnly = 0333\nconst readOnly = 0555\n"
  },
  {
    "path": "cmd/nash/main.go",
    "content": "// Package main has two sides:\n// - User mode: shell\n// - tool mode: unix socket server for handling namespace operations\n// When started, the program choses their side based on the argv[0].\n// The name \"nash\" indicates a user shell and the name -nashd- indicates\n// the namespace server tool.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/madlambda/nash\"\n)\n\nvar (\n\t// version is set at build time\n\tVersionString = \"No version provided\"\n\n\tversion     bool\n\tdebug       bool\n\tfile        string\n\tcommand     string\n\taddr        string\n\tnoInit      bool\n\tinteractive bool\n\tinstall \tstring\n)\n\nfunc init() {\n\tflag.BoolVar(&version, \"version\", false, \"Show version\")\n\tflag.BoolVar(&debug, \"debug\", false, \"enable debug\")\n\tflag.BoolVar(&noInit, \"noinit\", false, \"do not load init/init.sh file\")\n\tflag.StringVar(&command, \"c\", \"\", \"command to execute\")\n\tflag.StringVar(&install, \"install\", \"\", \"path of the library that you want to install (can be a single file)\")\n\tflag.BoolVar(&interactive, \"i\", false, \"Interactive mode (default if no args)\")\n\n\tif os.Args[0] == \"-nashd-\" || (len(os.Args) > 1 && os.Args[1] == \"-daemon\") {\n\t\tflag.Bool(\"daemon\", false, \"force enable nashd mode\")\n\t\tflag.StringVar(&addr, \"addr\", \"\", \"rcd unix file\")\n\t}\n}\n\nfunc main() {\n\tvar args []string\n\tvar shell *nash.Shell\n\tvar err error\n\n\tflag.Parse()\n\n\tif version {\n\t\tfmt.Printf(\"build tag: %s\\n\", VersionString)\n\t\treturn\n\t}\n\t\n\tif install != \"\" {\n\t\tfmt.Printf(\"installing library located at [%s]\\n\", install)\n\t\tnp, err := NashPath()\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"error[%s] getting NASHPATH, cant install library\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\terr = InstallLib(np, install)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"error[%s] installing library\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfmt.Println(\"installed with success\")\n\t\treturn\n\t}\n\n\tif len(flag.Args()) > 0 {\n\t\targs = flag.Args()\n\t\tfile = args[0]\n\t}\n\n\tif shell, err = initShell(); err != nil {\n\t\tgoto Error\n\t}\n\n\tshell.SetDebug(debug)\n\n\tif addr != \"\" {\n\t\tstartNashd(shell, addr)\n\t\treturn\n\t}\n\n\tif (file == \"\" && command == \"\") || interactive {\n\t\tif err = cli(shell); err != nil {\n\t\t\tgoto Error\n\t\t}\n\n\t\treturn\n\t}\n\n\tif file != \"\" {\n\t\tif err = shell.ExecFile(file, args...); err != nil {\n\t\t\tgoto Error\n\t\t}\n\t}\n\n\tif command != \"\" {\n\t\terr = shell.ExecuteString(\"<argument -c>\", command)\n\t\tif err != nil {\n\t\t\tgoto Error\n\t\t}\n\t}\n\nError:\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc initShell() (*nash.Shell, error) {\n\t\t\n\tnashpath, err := NashPath()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnashroot, err := NashRoot()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t\n\tos.Mkdir(nashpath, 0755)\n\treturn nash.New(nashpath, nashroot)\n}\n"
  },
  {
    "path": "cmd/nash/rpc.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\n\t\"github.com/madlambda/nash\"\n)\n\nfunc serveConn(sh *nash.Shell, conn net.Conn) {\n\tvar data [1024]byte\n\n\tfor {\n\n\t\tn, err := conn.Read(data[:])\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"Failed to read data: %s\", err.Error())\n\t\t\treturn\n\t\t}\n\n\t\tif string(data[0:n]) == \"quit\" {\n\t\t\treturn\n\t\t}\n\n\t\terr = sh.ExecuteString(\"-nashd-\", string(data[0:n]))\n\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"nashd: %s\\n\", err.Error())\n\n\t\t\t_, err = conn.Write([]byte(\"1\"))\n\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"Failed to send command status.\\n\")\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = conn.Write([]byte(\"0\"))\n\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"Failed to send command status.\\n\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc startNashd(sh *nash.Shell, socketPath string) {\n\tos.Remove(socketPath)\n\n\taddr := &net.UnixAddr{\n\t\tNet:  \"unix\",\n\t\tName: socketPath,\n\t}\n\n\tlistener, err := net.ListenUnix(\"unix\", addr)\n\n\tif err != nil {\n\t\tfmt.Printf(\"ERROR: %s\\n\", err.Error())\n\t\treturn\n\t}\n\n\t// Accept only one connection\n\tconn, err := listener.AcceptUnix()\n\n\tif err != nil {\n\t\tfmt.Printf(\"ERROR: %v\", err.Error())\n\t}\n\n\tserveConn(sh, conn)\n\tlistener.Close()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/.travis.yml",
    "content": "language: go\ngo:\n  - 1.5\n  - 1.7\nscript:\n  - GOOS=windows go install github.com/chzyer/readline/example/...\n  - GOOS=linux go install github.com/chzyer/readline/example/...\n  - GOOS=darwin go install github.com/chzyer/readline/example/...\n  - go test -race -v\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/CHANGELOG.md",
    "content": "# ChangeLog\n\n### 1.4 - 2016-07-25\n\n* [#60][60] Support dynamic autocompletion\n* Fix ANSI parser on Windows\n* Fix wrong column width in complete mode on Windows\n* Remove dependent package \"golang.org/x/crypto/ssh/terminal\"\n\n### 1.3 - 2016-05-09\n\n* [#38][38] add SetChildren for prefix completer interface\n* [#42][42] improve multiple lines compatibility\n* [#43][43] remove sub-package(runes) for gopkg compatiblity\n* [#46][46] Auto complete with space prefixed line\n* [#48][48]\tsupport suspend process (ctrl+Z)\n* [#49][49] fix bug that check equals with previous command\n* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty\n\n### 1.2 - 2016-03-05\n\n* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)\n* [#23][23], support stdin remapping\n* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.\n* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.\n* Supports performs even stdin/stdout is not a tty.\n* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api.\n* [#28][28], fixes the history is not working as expected.\n* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`\n\n### 1.1 - 2015-11-20\n\n* [#12][12] Add support for key `<Delete>`/`<Home>`/`<End>`\n* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`.\n* Bugs fixed for `PrefixCompleter`\n* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience.\n* Customable Interrupt/EOF prompt in `Config`\n* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices\n* Provides a new password user experience(`readline.ReadPasswordEx()`).\n\n### 1.0 - 2015-10-14\n\n* Initial public release.\n\n[12]: https://github.com/chzyer/readline/pull/12\n[17]: https://github.com/chzyer/readline/pull/17\n[23]: https://github.com/chzyer/readline/pull/23\n[27]: https://github.com/chzyer/readline/pull/27\n[28]: https://github.com/chzyer/readline/pull/28\n[33]: https://github.com/chzyer/readline/pull/33\n[38]: https://github.com/chzyer/readline/pull/38\n[42]: https://github.com/chzyer/readline/pull/42\n[43]: https://github.com/chzyer/readline/pull/43\n[46]: https://github.com/chzyer/readline/pull/46\n[48]: https://github.com/chzyer/readline/pull/48\n[49]: https://github.com/chzyer/readline/pull/49\n[53]: https://github.com/chzyer/readline/pull/53\n[60]: https://github.com/chzyer/readline/pull/60\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Chzyer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/README.md",
    "content": "[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline)\n[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)\n[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases)\n[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline)\n[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers)\n[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors)\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/chzyer/readline/assets/logo.png\" />\n<a href=\"https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m\" target=\"_blank\"><img src=\"https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png\" width=\"654\"/></a>\n<img src=\"https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png\" />\n</p>\n\nA powerful readline library in `Linux` `macOS` `Windows`\n\n## Guide\n\n* [Demo](example/readline-demo/readline-demo.go)\n* [Shortcut](doc/shortcut.md)\n\n## Repos using readline\n\t\n[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach)\n[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire)\n[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman)\n[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp)\n[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell)\n[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto)\n[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001)\n[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p)\n[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg)\n\n\n## Feedback\n\nIf you have any questions, please submit a github issue and any pull requests is welcomed :)\n\n* [https://twitter.com/chzyer](https://twitter.com/chzyer)  \n* [http://weibo.com/2145262190](http://weibo.com/2145262190)  \n\n\n## Backers\n\nLove Readline? Help me keep it alive by donating funds to cover project expenses!<br />\n[[Become a backer](https://opencollective.com/readline#backer)]\n\n<a href=\"https://opencollective.com/readline/backer/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/backer/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/backer/29/avatar.svg\"></a>\n\n\n## Sponsors\n\nBecome a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)]\n\n<a href=\"https://opencollective.com/readline/sponsor/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/9/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/10/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/10/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/11/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/11/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/12/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/12/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/13/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/13/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/14/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/14/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/15/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/15/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/16/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/16/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/17/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/17/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/18/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/18/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/19/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/19/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/20/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/20/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/21/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/21/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/22/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/22/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/23/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/23/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/24/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/24/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/25/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/25/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/26/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/26/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/27/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/27/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/28/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/28/avatar.svg\"></a>\n<a href=\"https://opencollective.com/readline/sponsor/29/website\" target=\"_blank\"><img src=\"https://opencollective.com/readline/sponsor/29/avatar.svg\"></a>\n\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/ansi_windows.go",
    "content": "// +build windows\n\npackage readline\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"unicode/utf8\"\n\t\"unsafe\"\n)\n\nconst (\n\t_                = uint16(0)\n\tCOLOR_FBLUE      = 0x0001\n\tCOLOR_FGREEN     = 0x0002\n\tCOLOR_FRED       = 0x0004\n\tCOLOR_FINTENSITY = 0x0008\n\n\tCOLOR_BBLUE      = 0x0010\n\tCOLOR_BGREEN     = 0x0020\n\tCOLOR_BRED       = 0x0040\n\tCOLOR_BINTENSITY = 0x0080\n\n\tCOMMON_LVB_UNDERSCORE = 0x8000\n)\n\nvar ColorTableFg = []word{\n\t0,                                       // 30: Black\n\tCOLOR_FRED,                              // 31: Red\n\tCOLOR_FGREEN,                            // 32: Green\n\tCOLOR_FRED | COLOR_FGREEN,               // 33: Yellow\n\tCOLOR_FBLUE,                             // 34: Blue\n\tCOLOR_FRED | COLOR_FBLUE,                // 35: Magenta\n\tCOLOR_FGREEN | COLOR_FBLUE,              // 36: Cyan\n\tCOLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White\n}\n\nvar ColorTableBg = []word{\n\t0,                                       // 40: Black\n\tCOLOR_BRED,                              // 41: Red\n\tCOLOR_BGREEN,                            // 42: Green\n\tCOLOR_BRED | COLOR_BGREEN,               // 43: Yellow\n\tCOLOR_BBLUE,                             // 44: Blue\n\tCOLOR_BRED | COLOR_BBLUE,                // 45: Magenta\n\tCOLOR_BGREEN | COLOR_BBLUE,              // 46: Cyan\n\tCOLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White\n}\n\ntype ANSIWriter struct {\n\ttarget io.Writer\n\twg     sync.WaitGroup\n\tctx    *ANSIWriterCtx\n\tsync.Mutex\n}\n\nfunc NewANSIWriter(w io.Writer) *ANSIWriter {\n\ta := &ANSIWriter{\n\t\ttarget: w,\n\t\tctx:    NewANSIWriterCtx(w),\n\t}\n\treturn a\n}\n\nfunc (a *ANSIWriter) Close() error {\n\ta.wg.Wait()\n\treturn nil\n}\n\ntype ANSIWriterCtx struct {\n\tisEsc     bool\n\tisEscSeq  bool\n\targ       []string\n\ttarget    *bufio.Writer\n\twantFlush bool\n}\n\nfunc NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {\n\treturn &ANSIWriterCtx{\n\t\ttarget: bufio.NewWriter(target),\n\t}\n}\n\nfunc (a *ANSIWriterCtx) Flush() {\n\ta.target.Flush()\n}\n\nfunc (a *ANSIWriterCtx) process(r rune) bool {\n\tif a.wantFlush {\n\t\tif r == 0 || r == CharEsc {\n\t\t\ta.wantFlush = false\n\t\t\ta.target.Flush()\n\t\t}\n\t}\n\tif a.isEscSeq {\n\t\ta.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)\n\t\treturn true\n\t}\n\n\tswitch r {\n\tcase CharEsc:\n\t\ta.isEsc = true\n\tcase '[':\n\t\tif a.isEsc {\n\t\t\ta.arg = nil\n\t\t\ta.isEscSeq = true\n\t\t\ta.isEsc = false\n\t\t\tbreak\n\t\t}\n\t\tfallthrough\n\tdefault:\n\t\ta.target.WriteRune(r)\n\t\ta.wantFlush = true\n\t}\n\treturn true\n}\n\nfunc (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {\n\targ := *argptr\n\tvar err error\n\n\tif r >= 'A' && r <= 'D' {\n\t\tcount := short(GetInt(arg, 1))\n\t\tinfo, err := GetConsoleScreenBufferInfo()\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tswitch r {\n\t\tcase 'A': // up\n\t\t\tinfo.dwCursorPosition.y -= count\n\t\tcase 'B': // down\n\t\t\tinfo.dwCursorPosition.y += count\n\t\tcase 'C': // right\n\t\t\tinfo.dwCursorPosition.x += count\n\t\tcase 'D': // left\n\t\t\tinfo.dwCursorPosition.x -= count\n\t\t}\n\t\tSetConsoleCursorPosition(&info.dwCursorPosition)\n\t\treturn false\n\t}\n\n\tswitch r {\n\tcase 'J':\n\t\tkillLines()\n\tcase 'K':\n\t\teraseLine()\n\tcase 'm':\n\t\tcolor := word(0)\n\t\tfor _, item := range arg {\n\t\t\tvar c int\n\t\t\tc, err = strconv.Atoi(item)\n\t\t\tif err != nil {\n\t\t\t\tw.WriteString(\"[\" + strings.Join(arg, \";\") + \"m\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif c >= 30 && c < 40 {\n\t\t\t\tcolor ^= COLOR_FINTENSITY\n\t\t\t\tcolor |= ColorTableFg[c-30]\n\t\t\t} else if c >= 40 && c < 50 {\n\t\t\t\tcolor ^= COLOR_BINTENSITY\n\t\t\t\tcolor |= ColorTableBg[c-40]\n\t\t\t} else if c == 4 {\n\t\t\t\tcolor |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]\n\t\t\t} else { // unknown code treat as reset\n\t\t\t\tcolor = ColorTableFg[7]\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tkernel.SetConsoleTextAttribute(stdout, uintptr(color))\n\tcase '\\007': // set title\n\tcase ';':\n\t\tif len(arg) == 0 || arg[len(arg)-1] != \"\" {\n\t\t\targ = append(arg, \"\")\n\t\t\t*argptr = arg\n\t\t}\n\t\treturn true\n\tdefault:\n\t\tif len(arg) == 0 {\n\t\t\targ = append(arg, \"\")\n\t\t}\n\t\targ[len(arg)-1] += string(r)\n\t\t*argptr = arg\n\t\treturn true\n\t}\n\t*argptr = nil\n\treturn false\n}\n\nfunc (a *ANSIWriter) Write(b []byte) (int, error) {\n\ta.Lock()\n\tdefer a.Unlock()\n\n\toff := 0\n\tfor len(b) > off {\n\t\tr, size := utf8.DecodeRune(b[off:])\n\t\tif size == 0 {\n\t\t\treturn off, io.ErrShortWrite\n\t\t}\n\t\toff += size\n\t\ta.ctx.process(r)\n\t}\n\ta.ctx.Flush()\n\treturn off, nil\n}\n\nfunc killLines() error {\n\tsbi, err := GetConsoleScreenBufferInfo()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsize := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x\n\tsize += sbi.dwCursorPosition.x\n\n\tvar written int\n\tkernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),\n\t\tuintptr(size),\n\t\tsbi.dwCursorPosition.ptr(),\n\t\tuintptr(unsafe.Pointer(&written)),\n\t)\n\treturn kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),\n\t\tuintptr(size),\n\t\tsbi.dwCursorPosition.ptr(),\n\t\tuintptr(unsafe.Pointer(&written)),\n\t)\n}\n\nfunc eraseLine() error {\n\tsbi, err := GetConsoleScreenBufferInfo()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsize := sbi.dwSize.x\n\tsbi.dwCursorPosition.x = 0\n\tvar written int\n\treturn kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),\n\t\tuintptr(size),\n\t\tsbi.dwCursorPosition.ptr(),\n\t\tuintptr(unsafe.Pointer(&written)),\n\t)\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/complete.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n)\n\ntype AutoCompleter interface {\n\t// Readline will pass the whole line and current offset to it\n\t// Completer need to pass all the candidates, and how long they shared the same characters in line\n\t// Example:\n\t//   [go, git, git-shell, grep]\n\t//   Do(\"g\", 1) => [\"o\", \"it\", \"it-shell\", \"rep\"], 1\n\t//   Do(\"gi\", 2) => [\"t\", \"t-shell\"], 2\n\t//   Do(\"git\", 3) => [\"\", \"-shell\"], 3\n\tDo(line []rune, pos int) (newLine [][]rune, length int)\n}\n\ntype TabCompleter struct{}\n\nfunc (t *TabCompleter) Do([]rune, int) ([][]rune, int) {\n\treturn [][]rune{[]rune(\"\\t\")}, 0\n}\n\ntype opCompleter struct {\n\tw     io.Writer\n\top    *Operation\n\twidth int\n\n\tinCompleteMode  bool\n\tinSelectMode    bool\n\tcandidate       [][]rune\n\tcandidateSource []rune\n\tcandidateOff    int\n\tcandidateChoise int\n\tcandidateColNum int\n}\n\nfunc newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {\n\treturn &opCompleter{\n\t\tw:     w,\n\t\top:    op,\n\t\twidth: width,\n\t}\n}\n\nfunc (o *opCompleter) doSelect() {\n\tif len(o.candidate) == 1 {\n\t\to.op.buf.WriteRunes(o.candidate[0])\n\t\to.ExitCompleteMode(false)\n\t\treturn\n\t}\n\to.nextCandidate(1)\n\to.CompleteRefresh()\n}\n\nfunc (o *opCompleter) nextCandidate(i int) {\n\to.candidateChoise += i\n\to.candidateChoise = o.candidateChoise % len(o.candidate)\n\tif o.candidateChoise < 0 {\n\t\to.candidateChoise = len(o.candidate) + o.candidateChoise\n\t}\n}\n\nfunc (o *opCompleter) OnComplete() bool {\n\tif o.width == 0 {\n\t\treturn false\n\t}\n\tif o.IsInCompleteSelectMode() {\n\t\to.doSelect()\n\t\treturn true\n\t}\n\n\tbuf := o.op.buf\n\trs := buf.Runes()\n\n\tif o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {\n\t\to.EnterCompleteSelectMode()\n\t\to.doSelect()\n\t\treturn true\n\t}\n\n\to.ExitCompleteSelectMode()\n\to.candidateSource = rs\n\tnewLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)\n\tif len(newLines) == 0 {\n\t\to.ExitCompleteMode(false)\n\t\treturn true\n\t}\n\n\t// only Aggregate candidates in non-complete mode\n\tif !o.IsInCompleteMode() {\n\t\tif len(newLines) == 1 {\n\t\t\tif offset > 0 {\n\t\t\t\tbuf.Set([]rune(string(buf.Runes())[0:offset] + string(newLines[0])))\n\t\t\t} else {\n\t\t\t\tbuf.WriteRunes(newLines[0])\n\t\t\t}\n\n\t\t\to.ExitCompleteMode(false)\n\t\t\treturn true\n\t\t}\n\n\t\tsame, size := runes.Aggregate(newLines)\n\t\tif size > 0 {\n\t\t\tbuf.WriteRunes(same)\n\t\t\to.ExitCompleteMode(false)\n\t\t\treturn true\n\t\t}\n\t}\n\n\to.EnterCompleteMode(offset, newLines)\n\treturn true\n}\n\nfunc (o *opCompleter) IsInCompleteSelectMode() bool {\n\treturn o.inSelectMode\n}\n\nfunc (o *opCompleter) IsInCompleteMode() bool {\n\treturn o.inCompleteMode\n}\n\nfunc (o *opCompleter) HandleCompleteSelect(r rune) bool {\n\tnext := true\n\tswitch r {\n\tcase CharEnter, CharCtrlJ:\n\t\tnext = false\n\t\to.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])\n\t\to.ExitCompleteMode(false)\n\tcase CharLineStart:\n\t\tnum := o.candidateChoise % o.candidateColNum\n\t\to.nextCandidate(-num)\n\tcase CharLineEnd:\n\t\tnum := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1\n\t\to.candidateChoise += num\n\t\tif o.candidateChoise >= len(o.candidate) {\n\t\t\to.candidateChoise = len(o.candidate) - 1\n\t\t}\n\tcase CharBackspace:\n\t\to.ExitCompleteSelectMode()\n\t\tnext = false\n\tcase CharTab, CharForward:\n\t\to.doSelect()\n\tcase CharBell, CharInterrupt:\n\t\to.ExitCompleteMode(true)\n\t\tnext = false\n\tcase CharNext:\n\t\ttmpChoise := o.candidateChoise + o.candidateColNum\n\t\tif tmpChoise >= o.getMatrixSize() {\n\t\t\ttmpChoise -= o.getMatrixSize()\n\t\t} else if tmpChoise >= len(o.candidate) {\n\t\t\ttmpChoise += o.candidateColNum\n\t\t\ttmpChoise -= o.getMatrixSize()\n\t\t}\n\t\to.candidateChoise = tmpChoise\n\tcase CharBackward:\n\t\to.nextCandidate(-1)\n\tcase CharPrev:\n\t\ttmpChoise := o.candidateChoise - o.candidateColNum\n\t\tif tmpChoise < 0 {\n\t\t\ttmpChoise += o.getMatrixSize()\n\t\t\tif tmpChoise >= len(o.candidate) {\n\t\t\t\ttmpChoise -= o.candidateColNum\n\t\t\t}\n\t\t}\n\t\to.candidateChoise = tmpChoise\n\tdefault:\n\t\tnext = false\n\t\to.ExitCompleteSelectMode()\n\t}\n\tif next {\n\t\to.CompleteRefresh()\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (o *opCompleter) getMatrixSize() int {\n\tline := len(o.candidate) / o.candidateColNum\n\tif len(o.candidate)%o.candidateColNum != 0 {\n\t\tline++\n\t}\n\treturn line * o.candidateColNum\n}\n\nfunc (o *opCompleter) OnWidthChange(newWidth int) {\n\to.width = newWidth\n}\n\nfunc (o *opCompleter) CompleteRefresh() {\n\tif !o.inCompleteMode {\n\t\treturn\n\t}\n\tlineCnt := o.op.buf.CursorLineCount()\n\tcolWidth := 0\n\tfor _, c := range o.candidate {\n\t\tw := runes.WidthAll(c)\n\t\tif w > colWidth {\n\t\t\tcolWidth = w\n\t\t}\n\t}\n\tcolWidth += o.candidateOff + 1\n\tsame := o.op.buf.RuneSlice(-o.candidateOff)\n\n\t// -1 to avoid reach the end of line\n\twidth := o.width - 1\n\tcolNum := width / colWidth\n\tcolWidth += (width - (colWidth * colNum)) / colNum\n\n\to.candidateColNum = colNum\n\tbuf := bufio.NewWriter(o.w)\n\tbuf.Write(bytes.Repeat([]byte(\"\\n\"), lineCnt))\n\n\tcolIdx := 0\n\tlines := 1\n\tbuf.WriteString(\"\\033[J\")\n\tfor idx, c := range o.candidate {\n\t\tinSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()\n\t\tif inSelect {\n\t\t\tbuf.WriteString(\"\\033[30;47m\")\n\t\t}\n\t\tbuf.WriteString(string(same))\n\t\tbuf.WriteString(string(c))\n\t\tbuf.Write(bytes.Repeat([]byte(\" \"), colWidth-len(c)-len(same)))\n\n\t\tif inSelect {\n\t\t\tbuf.WriteString(\"\\033[0m\")\n\t\t}\n\n\t\tcolIdx++\n\t\tif colIdx == colNum {\n\t\t\tbuf.WriteString(\"\\n\")\n\t\t\tlines++\n\t\t\tcolIdx = 0\n\t\t}\n\t}\n\n\t// move back\n\tfmt.Fprintf(buf, \"\\033[%dA\\r\", lineCnt-1+lines)\n\tfmt.Fprintf(buf, \"\\033[%dC\", o.op.buf.idx+o.op.buf.PromptLen())\n\tbuf.Flush()\n}\n\nfunc (o *opCompleter) aggCandidate(candidate [][]rune) int {\n\toffset := 0\n\tfor i := 0; i < len(candidate[0]); i++ {\n\t\tfor j := 0; j < len(candidate)-1; j++ {\n\t\t\tif i > len(candidate[j]) {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t\tif candidate[j][i] != candidate[j+1][i] {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t}\n\t\toffset = i\n\t}\naggregate:\n\treturn offset\n}\n\nfunc (o *opCompleter) EnterCompleteSelectMode() {\n\to.inSelectMode = true\n\to.candidateChoise = -1\n\to.CompleteRefresh()\n}\n\nfunc (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {\n\to.inCompleteMode = true\n\to.candidate = candidate\n\to.candidateOff = offset\n\to.CompleteRefresh()\n}\n\nfunc (o *opCompleter) ExitCompleteSelectMode() {\n\to.inSelectMode = false\n\to.candidate = nil\n\to.candidateChoise = -1\n\to.candidateOff = -1\n\to.candidateSource = nil\n}\n\nfunc (o *opCompleter) ExitCompleteMode(revent bool) {\n\to.inCompleteMode = false\n\to.ExitCompleteSelectMode()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/complete_helper.go",
    "content": "package readline\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n)\n\n// Caller type for dynamic completion\ntype DynamicCompleteFunc func(string) []string\n\ntype PrefixCompleterInterface interface {\n\tPrint(prefix string, level int, buf *bytes.Buffer)\n\tDo(line []rune, pos int) (newLine [][]rune, length int)\n\tGetName() []rune\n\tGetChildren() []PrefixCompleterInterface\n\tSetChildren(children []PrefixCompleterInterface)\n}\n\ntype DynamicPrefixCompleterInterface interface {\n\tPrefixCompleterInterface\n\tIsDynamic() bool\n\tGetDynamicNames(line []rune) [][]rune\n}\n\ntype PrefixCompleter struct {\n\tName     []rune\n\tDynamic  bool\n\tCallback DynamicCompleteFunc\n\tChildren []PrefixCompleterInterface\n}\n\nfunc (p *PrefixCompleter) Tree(prefix string) string {\n\tbuf := bytes.NewBuffer(nil)\n\tp.Print(prefix, 0, buf)\n\treturn buf.String()\n}\n\nfunc Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) {\n\tif strings.TrimSpace(string(p.GetName())) != \"\" {\n\t\tbuf.WriteString(prefix)\n\t\tif level > 0 {\n\t\t\tbuf.WriteString(\"├\")\n\t\t\tbuf.WriteString(strings.Repeat(\"─\", (level*4)-2))\n\t\t\tbuf.WriteString(\" \")\n\t\t}\n\t\tbuf.WriteString(string(p.GetName()) + \"\\n\")\n\t\tlevel++\n\t}\n\tfor _, ch := range p.GetChildren() {\n\t\tch.Print(prefix, level, buf)\n\t}\n}\n\nfunc (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {\n\tPrint(p, prefix, level, buf)\n}\n\nfunc (p *PrefixCompleter) IsDynamic() bool {\n\treturn p.Dynamic\n}\n\nfunc (p *PrefixCompleter) GetName() []rune {\n\treturn p.Name\n}\n\nfunc (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune {\n\tvar names = [][]rune{}\n\tfor _, name := range p.Callback(string(line)) {\n\t\tnames = append(names, []rune(name+\" \"))\n\t}\n\treturn names\n}\n\nfunc (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {\n\treturn p.Children\n}\n\nfunc (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) {\n\tp.Children = children\n}\n\nfunc NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter {\n\treturn PcItem(\"\", pc...)\n}\n\nfunc PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {\n\tname += \" \"\n\treturn &PrefixCompleter{\n\t\tName:     []rune(name),\n\t\tDynamic:  false,\n\t\tChildren: pc,\n\t}\n}\n\nfunc PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {\n\treturn &PrefixCompleter{\n\t\tCallback: callback,\n\t\tDynamic:  true,\n\t\tChildren: pc,\n\t}\n}\n\nfunc (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {\n\treturn doInternal(p, line, pos, line)\n}\n\nfunc Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) {\n\treturn doInternal(p, line, pos, line)\n}\n\nfunc doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {\n\tline = runes.TrimSpaceLeft(line[:pos])\n\tgoNext := false\n\tvar lineCompleter PrefixCompleterInterface\n\tfor _, child := range p.GetChildren() {\n\t\tchildNames := make([][]rune, 1)\n\n\t\tchildDynamic, ok := child.(DynamicPrefixCompleterInterface)\n\t\tif ok && childDynamic.IsDynamic() {\n\t\t\tchildNames = childDynamic.GetDynamicNames(origLine)\n\t\t} else {\n\t\t\tchildNames[0] = child.GetName()\n\t\t}\n\n\t\tfor _, childName := range childNames {\n\t\t\tif len(line) >= len(childName) {\n\t\t\t\tif runes.HasPrefix(line, childName) {\n\t\t\t\t\tif len(line) == len(childName) {\n\t\t\t\t\t\tnewLine = append(newLine, []rune{' '})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewLine = append(newLine, childName)\n\t\t\t\t\t}\n\t\t\t\t\toffset = len(childName)\n\t\t\t\t\tlineCompleter = child\n\t\t\t\t\tgoNext = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif runes.HasPrefix(childName, line) {\n\t\t\t\t\tnewLine = append(newLine, childName[len(line):])\n\t\t\t\t\toffset = len(line)\n\t\t\t\t\tlineCompleter = child\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(newLine) != 1 {\n\t\treturn\n\t}\n\n\ttmpLine := make([]rune, 0, len(line))\n\tfor i := offset; i < len(line); i++ {\n\t\tif line[i] == ' ' {\n\t\t\tcontinue\n\t\t}\n\n\t\ttmpLine = append(tmpLine, line[i:]...)\n\t\treturn doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)\n\t}\n\n\tif goNext {\n\t\treturn doInternal(lineCompleter, nil, 0, origLine)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/complete_segment.go",
    "content": "package readline\n\ntype SegmentCompleter interface {\n\t// a\n\t// |- a1\n\t// |--- a11\n\t// |- a2\n\t// b\n\t// input:\n\t//   DoTree([], 0) [a, b]\n\t//   DoTree([a], 1) [a]\n\t//   DoTree([a, ], 0) [a1, a2]\n\t//   DoTree([a, a], 1) [a1, a2]\n\t//   DoTree([a, a1], 2) [a1]\n\t//   DoTree([a, a1, ], 0) [a11]\n\t//   DoTree([a, a1, a], 1) [a11]\n\tDoSegment([][]rune, int) [][]rune\n}\n\ntype dumpSegmentCompleter struct {\n\tf func([][]rune, int) [][]rune\n}\n\nfunc (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune {\n\treturn d.f(segment, n)\n}\n\nfunc SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter {\n\treturn &SegmentComplete{&dumpSegmentCompleter{f}}\n}\n\nfunc SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete {\n\treturn &SegmentComplete{\n\t\tSegmentCompleter: completer,\n\t}\n}\n\ntype SegmentComplete struct {\n\tSegmentCompleter\n}\n\nfunc RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) {\n\tret := make([][]rune, 0, len(cands))\n\tlastSegment := segments[len(segments)-1]\n\tfor _, cand := range cands {\n\t\tif !runes.HasPrefix(cand, lastSegment) {\n\t\t\tcontinue\n\t\t}\n\t\tret = append(ret, cand[len(lastSegment):])\n\t}\n\treturn ret, idx\n}\n\nfunc SplitSegment(line []rune, pos int) ([][]rune, int) {\n\tsegs := [][]rune{}\n\tlastIdx := -1\n\tline = line[:pos]\n\tpos = 0\n\tfor idx, l := range line {\n\t\tif l == ' ' {\n\t\t\tpos = 0\n\t\t\tsegs = append(segs, line[lastIdx+1:idx])\n\t\t\tlastIdx = idx\n\t\t} else {\n\t\t\tpos++\n\t\t}\n\t}\n\tsegs = append(segs, line[lastIdx+1:])\n\treturn segs, pos\n}\n\nfunc (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) {\n\n\tsegment, idx := SplitSegment(line, pos)\n\n\tcands := c.DoSegment(segment, idx)\n\tnewLine, offset = RetSegment(segment, cands, idx)\n\tfor idx := range newLine {\n\t\tnewLine[idx] = append(newLine[idx], ' ')\n\t}\n\treturn newLine, offset\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/complete_segment_test.go",
    "content": "package readline\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/chzyer/test\"\n)\n\nfunc rs(s [][]rune) []string {\n\tret := make([]string, len(s))\n\tfor idx, ss := range s {\n\t\tret[idx] = string(ss)\n\t}\n\treturn ret\n}\n\nfunc sr(s ...string) [][]rune {\n\tret := make([][]rune, len(s))\n\tfor idx, ss := range s {\n\t\tret[idx] = []rune(ss)\n\t}\n\treturn ret\n}\n\nfunc TestRetSegment(t *testing.T) {\n\tdefer test.New(t)\n\t// a\n\t// |- a1\n\t// |--- a11\n\t// |--- a12\n\t// |- a2\n\t// |--- a21\n\t// b\n\t// add\n\t// adddomain\n\tret := []struct {\n\t\tSegments [][]rune\n\t\tCands    [][]rune\n\t\tidx      int\n\t\tRet      [][]rune\n\t\tpos      int\n\t}{\n\t\t{sr(\"\"), sr(\"a\", \"b\", \"add\", \"adddomain\"), 0, sr(\"a\", \"b\", \"add\", \"adddomain\"), 0},\n\t\t{sr(\"a\"), sr(\"a\", \"add\", \"adddomain\"), 1, sr(\"\", \"dd\", \"dddomain\"), 1},\n\t\t{sr(\"a\", \"\"), sr(\"a1\", \"a2\"), 0, sr(\"a1\", \"a2\"), 0},\n\t\t{sr(\"a\", \"a\"), sr(\"a1\", \"a2\"), 1, sr(\"1\", \"2\"), 1},\n\t\t{sr(\"a\", \"a1\"), sr(\"a1\"), 2, sr(\"\"), 2},\n\t\t{sr(\"add\"), sr(\"add\", \"adddomain\"), 2, sr(\"\", \"domain\"), 2},\n\t}\n\tfor idx, r := range ret {\n\t\tret, pos := RetSegment(r.Segments, r.Cands, r.idx)\n\t\ttest.Equal(ret, r.Ret, fmt.Errorf(\"%v\", idx))\n\t\ttest.Equal(pos, r.pos, fmt.Errorf(\"%v\", idx))\n\t}\n}\n\nfunc TestSplitSegment(t *testing.T) {\n\tdefer test.New(t)\n\t// a\n\t// |- a1\n\t// |--- a11\n\t// |--- a12\n\t// |- a2\n\t// |--- a21\n\t// b\n\tret := []struct {\n\t\tLine     string\n\t\tPos      int\n\t\tSegments [][]rune\n\t\tIdx      int\n\t}{\n\t\t{\"\", 0, sr(\"\"), 0},\n\t\t{\"a\", 1, sr(\"a\"), 1},\n\t\t{\"a \", 2, sr(\"a\", \"\"), 0},\n\t\t{\"a a\", 3, sr(\"a\", \"a\"), 1},\n\t\t{\"a a1\", 4, sr(\"a\", \"a1\"), 2},\n\t\t{\"a a1 \", 5, sr(\"a\", \"a1\", \"\"), 0},\n\t}\n\n\tfor i, r := range ret {\n\t\tret, idx := SplitSegment([]rune(r.Line), r.Pos)\n\t\ttest.Equal(rs(ret), rs(r.Segments), fmt.Errorf(\"%v\", i))\n\t\ttest.Equal(idx, r.Idx, fmt.Errorf(\"%v\", i))\n\t}\n}\n\ntype Tree struct {\n\tName     string\n\tChildren []Tree\n}\n\nfunc TestSegmentCompleter(t *testing.T) {\n\tdefer test.New(t)\n\n\ttree := Tree{\"\", []Tree{\n\t\t{\"a\", []Tree{\n\t\t\t{\"a1\", []Tree{\n\t\t\t\t{\"a11\", nil},\n\t\t\t\t{\"a12\", nil},\n\t\t\t}},\n\t\t\t{\"a2\", []Tree{\n\t\t\t\t{\"a21\", nil},\n\t\t\t}},\n\t\t}},\n\t\t{\"b\", nil},\n\t\t{\"route\", []Tree{\n\t\t\t{\"add\", nil},\n\t\t\t{\"adddomain\", nil},\n\t\t}},\n\t}}\n\ts := SegmentFunc(func(ret [][]rune, n int) [][]rune {\n\t\ttree := tree\n\tmain:\n\t\tfor level := 0; level < len(ret)-1; {\n\t\t\tname := string(ret[level])\n\t\t\tfor _, t := range tree.Children {\n\t\t\t\tif t.Name == name {\n\t\t\t\t\ttree = t\n\t\t\t\t\tlevel++\n\t\t\t\t\tcontinue main\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tret = make([][]rune, len(tree.Children))\n\t\tfor idx, r := range tree.Children {\n\t\t\tret[idx] = []rune(r.Name)\n\t\t}\n\t\treturn ret\n\t})\n\n\t// a\n\t// |- a1\n\t// |--- a11\n\t// |--- a12\n\t// |- a2\n\t// |--- a21\n\t// b\n\tret := []struct {\n\t\tLine  string\n\t\tPos   int\n\t\tRet   [][]rune\n\t\tShare int\n\t}{\n\t\t{\"\", 0, sr(\"a\", \"b\", \"route\"), 0},\n\t\t{\"a\", 1, sr(\"\"), 1},\n\t\t{\"a \", 2, sr(\"a1\", \"a2\"), 0},\n\t\t{\"a a\", 3, sr(\"1\", \"2\"), 1},\n\t\t{\"a a1\", 4, sr(\"\"), 2},\n\t\t{\"a a1 \", 5, sr(\"a11\", \"a12\"), 0},\n\t\t{\"a a1 a\", 6, sr(\"11\", \"12\"), 1},\n\t\t{\"a a1 a1\", 7, sr(\"1\", \"2\"), 2},\n\t\t{\"a a1 a11\", 8, sr(\"\"), 3},\n\t\t{\"route add\", 9, sr(\"\", \"domain\"), 3},\n\t}\n\tfor _, r := range ret {\n\t\tfor idx, rr := range r.Ret {\n\t\t\tr.Ret[idx] = append(rr, ' ')\n\t\t}\n\t}\n\tfor i, r := range ret {\n\t\tnewLine, length := s.Do([]rune(r.Line), r.Pos)\n\t\ttest.Equal(rs(newLine), rs(r.Ret), fmt.Errorf(\"%v\", i))\n\t\ttest.Equal(length, r.Share, fmt.Errorf(\"%v\", i))\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/doc/shortcut.md",
    "content": "## Readline Shortcut\n\n`Meta`+`B` means press `Esc` and `n` separately.  \nUsers can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B`  \nNotice: `Meta`+`B` is equals with `Alt`+`B` in windows.\n\n* Shortcut in normal mode\n\n| Shortcut           | Comment                           |\n| ------------------ | --------------------------------- |\n| `Ctrl`+`A`         | Beginning of line                 |\n| `Ctrl`+`B` / `←`   | Backward one character            |\n| `Meta`+`B`         | Backward one word                 |\n| `Ctrl`+`C`         | Send io.EOF                       |\n| `Ctrl`+`D`         | Delete one character              |\n| `Meta`+`D`         | Delete one word                   |\n| `Ctrl`+`E`         | End of line                       |\n| `Ctrl`+`F` / `→`   | Forward one character             |\n| `Meta`+`F`         | Forward one word                  |\n| `Ctrl`+`G`         | Cancel                            |\n| `Ctrl`+`H`         | Delete previous character         |\n| `Ctrl`+`I` / `Tab` | Command line completion           |\n| `Ctrl`+`J`         | Line feed                         |\n| `Ctrl`+`K`         | Cut text to the end of line       |\n| `Ctrl`+`L`         | Clear screen                      |\n| `Ctrl`+`M`         | Same as Enter key                 |\n| `Ctrl`+`N` / `↓`   | Next line (in history)            |\n| `Ctrl`+`P` / `↑`   | Prev line (in history)            |\n| `Ctrl`+`R`         | Search backwards in history       |\n| `Ctrl`+`S`         | Search forwards in history        |\n| `Ctrl`+`T`         | Transpose characters              |\n| `Meta`+`T`         | Transpose words (TODO)            |\n| `Ctrl`+`U`         | Cut text to the beginning of line |\n| `Ctrl`+`W`         | Cut previous word                 |\n| `Backspace`        | Delete previous character         |\n| `Meta`+`Backspace` | Cut previous word                 |\n| `Enter`            | Line feed                         |\n\n\n* Shortcut in Search Mode (`Ctrl`+`S` or `Ctrl`+`r` to enter this mode)\n\n| Shortcut                | Comment                                 |\n| ----------------------- | --------------------------------------- |\n| `Ctrl`+`S`              | Search forwards in history              |\n| `Ctrl`+`R`              | Search backwards in history             |\n| `Ctrl`+`C` / `Ctrl`+`G` | Exit Search Mode and revert the history |\n| `Backspace`             | Delete previous character               |\n| Other                   | Exit Search Mode                        |\n\n* Shortcut in Complete Select Mode (double `Tab` to enter this mode)\n\n| Shortcut                | Comment                                  |\n| ----------------------- | ---------------------------------------- |\n| `Ctrl`+`F`              | Move Forward                             |\n| `Ctrl`+`B`              | Move Backward                            |\n| `Ctrl`+`N`              | Move to next line                        |\n| `Ctrl`+`P`              | Move to previous line                    |\n| `Ctrl`+`A`              | Move to the first candicate in current line |\n| `Ctrl`+`E`              | Move to the last candicate in current line |\n| `Tab` / `Enter`         | Use the word on cursor to complete       |\n| `Ctrl`+`C` / `Ctrl`+`G` | Exit Complete Select Mode                |\n| Other                   | Exit Complete Select Mode                |"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-demo/readline-demo.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/chzyer/readline\"\n)\n\nfunc usage(w io.Writer) {\n\tio.WriteString(w, \"commands:\\n\")\n\tio.WriteString(w, completer.Tree(\"    \"))\n}\n\n// Function constructor - constructs new function for listing given directory\nfunc listFiles(path string) func(string) []string {\n\treturn func(line string) []string {\n\t\tnames := make([]string, 0)\n\t\tfiles, _ := ioutil.ReadDir(path)\n\t\tfor _, f := range files {\n\t\t\tnames = append(names, f.Name())\n\t\t}\n\t\treturn names\n\t}\n}\n\nvar completer = readline.NewPrefixCompleter(\n\treadline.PcItem(\"mode\",\n\t\treadline.PcItem(\"vi\"),\n\t\treadline.PcItem(\"emacs\"),\n\t),\n\treadline.PcItem(\"login\"),\n\treadline.PcItem(\"say\",\n\t\treadline.PcItemDynamic(listFiles(\"./\"),\n\t\t\treadline.PcItem(\"with\",\n\t\t\t\treadline.PcItem(\"following\"),\n\t\t\t\treadline.PcItem(\"items\"),\n\t\t\t),\n\t\t),\n\t\treadline.PcItem(\"hello\"),\n\t\treadline.PcItem(\"bye\"),\n\t),\n\treadline.PcItem(\"setprompt\"),\n\treadline.PcItem(\"setpassword\"),\n\treadline.PcItem(\"bye\"),\n\treadline.PcItem(\"help\"),\n\treadline.PcItem(\"go\",\n\t\treadline.PcItem(\"build\", readline.PcItem(\"-o\"), readline.PcItem(\"-v\")),\n\t\treadline.PcItem(\"install\",\n\t\t\treadline.PcItem(\"-v\"),\n\t\t\treadline.PcItem(\"-vv\"),\n\t\t\treadline.PcItem(\"-vvv\"),\n\t\t),\n\t\treadline.PcItem(\"test\"),\n\t),\n\treadline.PcItem(\"sleep\"),\n)\n\nfunc main() {\n\tl, err := readline.NewEx(&readline.Config{\n\t\tPrompt:          \"\\033[31m»\\033[0m \",\n\t\tHistoryFile:     \"/tmp/readline.tmp\",\n\t\tAutoComplete:    completer,\n\t\tInterruptPrompt: \"^C\",\n\t\tEOFPrompt:       \"exit\",\n\n\t\tHistorySearchFold: true,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer l.Close()\n\n\tsetPasswordCfg := l.GenPasswordConfig()\n\tsetPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {\n\t\tl.SetPrompt(fmt.Sprintf(\"Enter password(%v): \", len(line)))\n\t\tl.Refresh()\n\t\treturn nil, 0, false\n\t})\n\n\tlog.SetOutput(l.Stderr())\n\tfor {\n\t\tline, err := l.Readline()\n\t\tif err == readline.ErrInterrupt {\n\t\t\tif len(line) == 0 {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if err == io.EOF {\n\t\t\tbreak\n\t\t}\n\n\t\tline = strings.TrimSpace(line)\n\t\tswitch {\n\t\tcase strings.HasPrefix(line, \"mode \"):\n\t\t\tswitch line[5:] {\n\t\t\tcase \"vi\":\n\t\t\t\tl.SetVimMode(true)\n\t\t\tcase \"emacs\":\n\t\t\t\tl.SetVimMode(false)\n\t\t\tdefault:\n\t\t\t\tprintln(\"invalid mode:\", line[5:])\n\t\t\t}\n\t\tcase line == \"mode\":\n\t\t\tif l.IsVimMode() {\n\t\t\t\tprintln(\"current mode: vim\")\n\t\t\t} else {\n\t\t\t\tprintln(\"current mode: emacs\")\n\t\t\t}\n\t\tcase line == \"login\":\n\t\t\tpswd, err := l.ReadPassword(\"please enter your password: \")\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tprintln(\"you enter:\", strconv.Quote(string(pswd)))\n\t\tcase line == \"help\":\n\t\t\tusage(l.Stderr())\n\t\tcase line == \"setpassword\":\n\t\t\tpswd, err := l.ReadPasswordWithConfig(setPasswordCfg)\n\t\t\tif err == nil {\n\t\t\t\tprintln(\"you set:\", strconv.Quote(string(pswd)))\n\t\t\t}\n\t\tcase strings.HasPrefix(line, \"setprompt\"):\n\t\t\tprompt := line[10:]\n\t\t\tif prompt == \"\" {\n\t\t\t\tlog.Println(\"setprompt <prompt>\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tl.SetPrompt(prompt)\n\t\tcase strings.HasPrefix(line, \"say\"):\n\t\t\tline := strings.TrimSpace(line[3:])\n\t\t\tif len(line) == 0 {\n\t\t\t\tlog.Println(\"say what?\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tgo func() {\n\t\t\t\tfor range time.Tick(time.Second) {\n\t\t\t\t\tlog.Println(line)\n\t\t\t\t}\n\t\t\t}()\n\t\tcase line == \"bye\":\n\t\t\tgoto exit\n\t\tcase line == \"sleep\":\n\t\t\tlog.Println(\"sleep 4 second\")\n\t\t\ttime.Sleep(4 * time.Second)\n\t\tcase line == \"\":\n\t\tdefault:\n\t\t\tlog.Println(\"you said:\", strconv.Quote(line))\n\t\t}\n\t}\nexit:\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-im/README.md",
    "content": "# readline-im\n\n![readline-im](https://dl.dropboxusercontent.com/s/52hc7bo92g3pgi5/03F93B8D-9B4B-4D35-BBAA-22FBDAC7F299-26173-000164AA33980001.gif?dl=0)\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-im/readline-im.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/chzyer/readline\"\n)\nimport \"log\"\n\nfunc main() {\n\trl, err := readline.NewEx(&readline.Config{\n\t\tUniqueEditLine: true,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer rl.Close()\n\n\trl.SetPrompt(\"username: \")\n\tusername, err := rl.Readline()\n\tif err != nil {\n\t\treturn\n\t}\n\trl.ResetHistory()\n\tlog.SetOutput(rl.Stderr())\n\n\tfmt.Fprintln(rl, \"Hi,\", username+\"! My name is Dave.\")\n\trl.SetPrompt(username + \"> \")\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\trand.Seed(time.Now().Unix())\n\tloop:\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-time.After(time.Duration(rand.Intn(20)) * 100 * time.Millisecond):\n\t\t\tcase <-done:\n\t\t\t\tbreak loop\n\t\t\t}\n\t\t\tlog.Println(\"Dave:\", \"hello\")\n\t\t}\n\t\tlog.Println(\"Dave:\", \"bye\")\n\t\tdone <- struct{}{}\n\t}()\n\n\tfor {\n\t\tln := rl.Line()\n\t\tif ln.CanContinue() {\n\t\t\tcontinue\n\t\t} else if ln.CanBreak() {\n\t\t\tbreak\n\t\t}\n\t\tlog.Println(username+\":\", ln.Line)\n\t}\n\trl.Clean()\n\tdone <- struct{}{}\n\t<-done\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-multiline/readline-multiline.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/chzyer/readline\"\n)\n\nfunc main() {\n\trl, err := readline.NewEx(&readline.Config{\n\t\tPrompt:                 \"> \",\n\t\tHistoryFile:            \"/tmp/readline-multiline\",\n\t\tDisableAutoSaveHistory: true,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer rl.Close()\n\n\tvar cmds []string\n\tfor {\n\t\tline, err := rl.Readline()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tline = strings.TrimSpace(line)\n\t\tif len(line) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcmds = append(cmds, line)\n\t\tif !strings.HasSuffix(line, \";\") {\n\t\t\trl.SetPrompt(\">>> \")\n\t\t\tcontinue\n\t\t}\n\t\tcmd := strings.Join(cmds, \" \")\n\t\tcmds = cmds[:0]\n\t\trl.SetPrompt(\"> \")\n\t\trl.SaveHistory(cmd)\n\t\tprintln(cmd)\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-pass-strength/readline-pass-strength.go",
    "content": "// This is a small example using readline to read a password\n// and check it's strength while typing using the zxcvbn library.\n// Depending on the strength the prompt is colored nicely to indicate strength.\n//\n// This file is licensed under the WTFPL:\n//\n//         DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n//                     Version 2, December 2004\n//\n//  Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n//\n//  Everyone is permitted to copy and distribute verbatim or modified\n//  copies of this license document, and changing it is allowed as long\n//  as the name is changed.\n//\n//             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n//    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n//\n//   0. You just DO WHAT THE FUCK YOU WANT TO.\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/chzyer/readline\"\n\tzxcvbn \"github.com/nbutton23/zxcvbn-go\"\n)\n\nconst (\n\tCyan          = 36\n\tGreen         = 32\n\tMagenta       = 35\n\tRed           = 31\n\tYellow        = 33\n\tBackgroundRed = 41\n)\n\n// Reset sequence\nvar ColorResetEscape = \"\\033[0m\"\n\n// ColorResetEscape translates a ANSI color number to a color escape.\nfunc ColorEscape(color int) string {\n\treturn fmt.Sprintf(\"\\033[0;%dm\", color)\n}\n\n// Colorize the msg using ANSI color escapes\nfunc Colorize(msg string, color int) string {\n\treturn ColorEscape(color) + msg + ColorResetEscape\n}\n\nfunc createStrengthPrompt(password []rune) string {\n\tsymbol, color := \"\", Red\n\tstrength := zxcvbn.PasswordStrength(string(password), nil)\n\n\tswitch {\n\tcase strength.Score <= 1:\n\t\tsymbol = \"✗\"\n\t\tcolor = Red\n\tcase strength.Score <= 2:\n\t\tsymbol = \"⚡\"\n\t\tcolor = Magenta\n\tcase strength.Score <= 3:\n\t\tsymbol = \"⚠\"\n\t\tcolor = Yellow\n\tcase strength.Score <= 4:\n\t\tsymbol = \"✔\"\n\t\tcolor = Green\n\t}\n\n\tprompt := Colorize(symbol, color)\n\tif strength.Entropy > 0 {\n\t\tentropy := fmt.Sprintf(\" %3.0f\", strength.Entropy)\n\t\tprompt += Colorize(entropy, Cyan)\n\t} else {\n\t\tprompt += Colorize(\" ENT\", Cyan)\n\t}\n\n\tprompt += Colorize(\" New Password: \", color)\n\treturn prompt\n}\n\nfunc main() {\n\trl, err := readline.New(\"\")\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer rl.Close()\n\n\tsetPasswordCfg := rl.GenPasswordConfig()\n\tsetPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {\n\t\trl.SetPrompt(createStrengthPrompt(line))\n\t\trl.Refresh()\n\t\treturn nil, 0, false\n\t})\n\n\tpswd, err := rl.ReadPasswordWithConfig(setPasswordCfg)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfmt.Println(\"Your password was:\", string(pswd))\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-remote/readline-remote-client/client.go",
    "content": "package main\n\nimport \"github.com/chzyer/readline\"\n\nfunc main() {\n\tif err := readline.DialRemote(\"tcp\", \":12344\"); err != nil {\n\t\tprintln(err.Error())\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/example/readline-remote/readline-remote-server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/chzyer/readline\"\n)\n\nfunc main() {\n\tcfg := &readline.Config{\n\t\tPrompt: \"readline-remote: \",\n\t}\n\thandleFunc := func(rl *readline.Instance) {\n\t\tfor {\n\t\t\tline, err := rl.Readline()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfmt.Fprintln(rl.Stdout(), \"receive:\"+line)\n\t\t}\n\t}\n\terr := readline.ListenRemote(\"tcp\", \":12344\", cfg, handleFunc)\n\tif err != nil {\n\t\tprintln(err.Error())\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/history.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"container/list\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n)\n\ntype hisItem struct {\n\tSource  []rune\n\tVersion int64\n\tTmp     []rune\n}\n\nfunc (h *hisItem) Clean() {\n\th.Source = nil\n\th.Tmp = nil\n}\n\ntype opHistory struct {\n\tcfg        *Config\n\thistory    *list.List\n\thistoryVer int64\n\tcurrent    *list.Element\n\tfd         *os.File\n\tfdLock     sync.Mutex\n}\n\nfunc newOpHistory(cfg *Config) (o *opHistory) {\n\to = &opHistory{\n\t\tcfg:     cfg,\n\t\thistory: list.New(),\n\t}\n\treturn o\n}\n\nfunc (o *opHistory) Reset() {\n\to.history = list.New()\n\to.current = nil\n}\n\nfunc (o *opHistory) IsHistoryClosed() bool {\n\to.fdLock.Lock()\n\tdefer o.fdLock.Unlock()\n\treturn o.fd.Fd() == ^(uintptr(0))\n}\n\nfunc (o *opHistory) Init() {\n\tif o.IsHistoryClosed() {\n\t\to.initHistory()\n\t}\n}\n\nfunc (o *opHistory) initHistory() {\n\tif o.cfg.HistoryFile != \"\" {\n\t\to.historyUpdatePath(o.cfg.HistoryFile)\n\t}\n}\n\n// only called by newOpHistory\nfunc (o *opHistory) historyUpdatePath(path string) {\n\to.fdLock.Lock()\n\tdefer o.fdLock.Unlock()\n\tf, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)\n\tif err != nil {\n\t\treturn\n\t}\n\to.fd = f\n\tr := bufio.NewReader(o.fd)\n\ttotal := 0\n\tfor ; ; total++ {\n\t\tline, err := r.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\t// ignore the empty line\n\t\tline = strings.TrimSpace(line)\n\t\tif len(line) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\to.Push([]rune(line))\n\t\to.Compact()\n\t}\n\tif total > o.cfg.HistoryLimit {\n\t\to.rewriteLocked()\n\t}\n\to.historyVer++\n\to.Push(nil)\n\treturn\n}\n\nfunc (o *opHistory) Compact() {\n\tfor o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {\n\t\to.history.Remove(o.history.Front())\n\t}\n}\n\nfunc (o *opHistory) Rewrite() {\n\to.fdLock.Lock()\n\tdefer o.fdLock.Unlock()\n\to.rewriteLocked()\n}\n\nfunc (o *opHistory) rewriteLocked() {\n\tif o.cfg.HistoryFile == \"\" {\n\t\treturn\n\t}\n\n\ttmpFile := o.cfg.HistoryFile + \".tmp\"\n\tfd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tbuf := bufio.NewWriter(fd)\n\tfor elem := o.history.Front(); elem != nil; elem = elem.Next() {\n\t\tbuf.WriteString(string(elem.Value.(*hisItem).Source))\n\t}\n\tbuf.Flush()\n\n\t// replace history file\n\tif err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {\n\t\tfd.Close()\n\t\treturn\n\t}\n\n\tif o.fd != nil {\n\t\to.fd.Close()\n\t}\n\t// fd is write only, just satisfy what we need.\n\to.fd = fd\n}\n\nfunc (o *opHistory) Close() {\n\to.fdLock.Lock()\n\tdefer o.fdLock.Unlock()\n\tif o.fd != nil {\n\t\to.fd.Close()\n\t}\n}\n\nfunc (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {\n\tfor elem := o.current; elem != nil; elem = elem.Prev() {\n\t\titem := o.showItem(elem.Value)\n\t\tif isNewSearch {\n\t\t\tstart += len(rs)\n\t\t}\n\t\tif elem == o.current {\n\t\t\tif len(item) >= start {\n\t\t\t\titem = item[:start]\n\t\t\t}\n\t\t}\n\t\tidx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)\n\t\tif idx < 0 {\n\t\t\tcontinue\n\t\t}\n\t\treturn idx, elem\n\t}\n\treturn -1, nil\n}\n\nfunc (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {\n\tfor elem := o.current; elem != nil; elem = elem.Next() {\n\t\titem := o.showItem(elem.Value)\n\t\tif isNewSearch {\n\t\t\tstart -= len(rs)\n\t\t\tif start < 0 {\n\t\t\t\tstart = 0\n\t\t\t}\n\t\t}\n\t\tif elem == o.current {\n\t\t\tif len(item)-1 >= start {\n\t\t\t\titem = item[start:]\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tidx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)\n\t\tif idx < 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif elem == o.current {\n\t\t\tidx += start\n\t\t}\n\t\treturn idx, elem\n\t}\n\treturn -1, nil\n}\n\nfunc (o *opHistory) showItem(obj interface{}) []rune {\n\titem := obj.(*hisItem)\n\tif item.Version == o.historyVer {\n\t\treturn item.Tmp\n\t}\n\treturn item.Source\n}\n\nfunc (o *opHistory) Prev() []rune {\n\tif o.current == nil {\n\t\treturn nil\n\t}\n\tcurrent := o.current.Prev()\n\tif current == nil {\n\t\treturn nil\n\t}\n\to.current = current\n\treturn runes.Copy(o.showItem(current.Value))\n}\n\nfunc (o *opHistory) Next() ([]rune, bool) {\n\tif o.current == nil {\n\t\treturn nil, false\n\t}\n\tcurrent := o.current.Next()\n\tif current == nil {\n\t\treturn nil, false\n\t}\n\n\to.current = current\n\treturn runes.Copy(o.showItem(current.Value)), true\n}\n\nfunc (o *opHistory) debug() {\n\tDebug(\"-------\")\n\tfor item := o.history.Front(); item != nil; item = item.Next() {\n\t\tDebug(fmt.Sprintf(\"%+v\", item.Value))\n\t}\n}\n\n// save history\nfunc (o *opHistory) New(current []rune) (err error) {\n\tcurrent = runes.Copy(current)\n\n\t// if just use last command without modify\n\t// just clean lastest history\n\tif back := o.history.Back(); back != nil {\n\t\tprev := back.Prev()\n\t\tif prev != nil {\n\t\t\tif runes.Equal(current, prev.Value.(*hisItem).Source) {\n\t\t\t\to.current = o.history.Back()\n\t\t\t\to.current.Value.(*hisItem).Clean()\n\t\t\t\to.historyVer++\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(current) == 0 {\n\t\to.current = o.history.Back()\n\t\tif o.current != nil {\n\t\t\to.current.Value.(*hisItem).Clean()\n\t\t\to.historyVer++\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif o.current != o.history.Back() {\n\t\t// move history item to current command\n\t\tcurrentItem := o.current.Value.(*hisItem)\n\t\t// set current to last item\n\t\to.current = o.history.Back()\n\n\t\tcurrent = runes.Copy(currentItem.Tmp)\n\t}\n\n\t// err only can be a IO error, just report\n\terr = o.Update(current, true)\n\n\t// push a new one to commit current command\n\to.historyVer++\n\to.Push(nil)\n\treturn\n}\n\nfunc (o *opHistory) Revert() {\n\to.historyVer++\n\to.current = o.history.Back()\n}\n\nfunc (o *opHistory) Update(s []rune, commit bool) (err error) {\n\to.fdLock.Lock()\n\tdefer o.fdLock.Unlock()\n\ts = runes.Copy(s)\n\tif o.current == nil {\n\t\to.Push(s)\n\t\to.Compact()\n\t\treturn\n\t}\n\tr := o.current.Value.(*hisItem)\n\tr.Version = o.historyVer\n\tif commit {\n\t\tr.Source = s\n\t\tif o.fd != nil {\n\t\t\t// just report the error\n\t\t\t_, err = o.fd.Write([]byte(string(r.Source) + \"\\n\"))\n\t\t}\n\t} else {\n\t\tr.Tmp = append(r.Tmp[:0], s...)\n\t}\n\to.current.Value = r\n\to.Compact()\n\treturn\n}\n\nfunc (o *opHistory) Push(s []rune) {\n\ts = runes.Copy(s)\n\telem := o.history.PushBack(&hisItem{Source: s})\n\to.current = elem\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/operation.go",
    "content": "package readline\n\nimport (\n\t\"errors\"\n\t\"io\"\n)\n\nvar (\n\tErrInterrupt = errors.New(\"Interrupt\")\n)\n\ntype InterruptError struct {\n\tLine []rune\n}\n\nfunc (*InterruptError) Error() string {\n\treturn \"Interrupted\"\n}\n\ntype Operation struct {\n\tcfg     *Config\n\tt       *Terminal\n\tbuf     *RuneBuffer\n\toutchan chan []rune\n\terrchan chan error\n\tw       io.Writer\n\n\thistory *opHistory\n\t*opCompleter\n\t*opPassword\n\t*opVim\n}\n\ntype wrapWriter struct {\n\tr      *Operation\n\tt      *Terminal\n\ttarget io.Writer\n}\n\nfunc (w *wrapWriter) Write(b []byte) (int, error) {\n\tif !w.t.IsReading() {\n\t\treturn w.target.Write(b)\n\t}\n\n\tvar (\n\t\tn   int\n\t\terr error\n\t)\n\tw.r.buf.Refresh(func() {\n\t\tn, err = w.target.Write(b)\n\t})\n\n\tif w.r.IsInCompleteMode() {\n\t\tw.r.CompleteRefresh()\n\t}\n\treturn n, err\n}\n\nfunc NewOperation(t *Terminal, cfg *Config) *Operation {\n\twidth := cfg.FuncGetWidth()\n\top := &Operation{\n\t\tt:       t,\n\t\tbuf:     NewRuneBuffer(t, cfg.Prompt, cfg, width),\n\t\toutchan: make(chan []rune),\n\t\terrchan: make(chan error),\n\t}\n\top.w = op.buf.w\n\top.SetConfig(cfg)\n\top.opVim = newVimMode(op)\n\top.opCompleter = newOpCompleter(op.buf.w, op, width)\n\top.opPassword = newOpPassword(op)\n\top.cfg.FuncOnWidthChanged(func() {\n\t\tnewWidth := cfg.FuncGetWidth()\n\t\top.opCompleter.OnWidthChange(newWidth)\n\t\top.buf.OnWidthChange(newWidth)\n\t})\n\tgo op.ioloop()\n\treturn op\n}\n\nfunc (o *Operation) SetPrompt(s string) {\n\to.buf.SetPrompt(s)\n}\n\nfunc (o *Operation) SetMaskRune(r rune) {\n\to.buf.SetMask(r)\n}\n\nfunc (o *Operation) ioloop() {\n\tfor {\n\t\tkeepInCompleteMode := false\n\t\tr := o.t.ReadRune()\n\t\tif r == 0 { // io.EOF\n\t\t\tif o.buf.Len() == 0 {\n\t\t\t\to.buf.Clean()\n\t\t\t\tselect {\n\t\t\t\tcase o.errchan <- io.EOF:\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\t// if stdin got io.EOF and there is something left in buffer,\n\t\t\t\t// let's flush them by sending CharEnter.\n\t\t\t\t// And we will got io.EOF int next loop.\n\t\t\t\tr = CharEnter\n\t\t\t}\n\t\t}\n\t\tisUpdateHistory := true\n\n\t\tif o.IsInCompleteSelectMode() {\n\t\t\tkeepInCompleteMode = o.HandleCompleteSelect(r)\n\t\t\tif keepInCompleteMode {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\to.buf.Refresh(nil)\n\t\t\tswitch r {\n\t\t\tcase CharEnter, CharCtrlJ:\n\t\t\t\to.history.Update(o.buf.Runes(), false)\n\t\t\t\tfallthrough\n\t\t\tcase CharInterrupt:\n\t\t\t\to.t.KickRead()\n\t\t\t\tfallthrough\n\t\t\tcase CharBell:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif o.IsEnableVimMode() {\n\t\t\tr = o.HandleVim(r, o.t.ReadRune)\n\t\t\tif r == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tswitch r {\n\t\tcase CharBell:\n\t\t\tif o.IsInCompleteMode() {\n\t\t\t\to.ExitCompleteMode(true)\n\t\t\t\to.buf.Refresh(nil)\n\t\t\t}\n\t\tcase CharTab:\n\t\t\tif o.cfg.AutoComplete == nil {\n\t\t\t\to.t.Bell()\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif o.OnComplete() {\n\t\t\t\tkeepInCompleteMode = true\n\t\t\t} else {\n\t\t\t\to.t.Bell()\n\t\t\t\tbreak\n\t\t\t}\n\n\t\tcase CharCtrlU:\n\t\t\to.buf.KillFront()\n\t\tcase CharKill:\n\t\t\to.buf.Kill()\n\t\t\tkeepInCompleteMode = true\n\t\tcase MetaForward:\n\t\t\to.buf.MoveToNextWord()\n\t\tcase CharTranspose:\n\t\t\to.buf.Transpose()\n\t\tcase MetaBackward:\n\t\t\to.buf.MoveToPrevWord()\n\t\tcase MetaDelete:\n\t\t\to.buf.DeleteWord()\n\t\tcase CharLineStart:\n\t\t\to.buf.MoveToLineStart()\n\t\tcase CharLineEnd:\n\t\t\to.buf.MoveToLineEnd()\n\t\tcase CharBackspace, CharCtrlH:\n\t\t\tif o.buf.Len() == 0 {\n\t\t\t\to.t.Bell()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\to.buf.Backspace()\n\t\t\tif o.IsInCompleteMode() {\n\t\t\t\to.OnComplete()\n\t\t\t}\n\t\tcase CharCtrlZ:\n\t\t\to.buf.Clean()\n\t\t\to.t.SleepToResume()\n\t\t\to.Refresh()\n\t\tcase CharCtrlL:\n\t\t\tClearScreen(o.w)\n\t\t\to.Refresh()\n\t\tcase MetaBackspace, CharCtrlW:\n\t\t\to.buf.BackEscapeWord()\n\t\tcase CharEnter, CharCtrlJ:\n\t\t\to.buf.MoveToLineEnd()\n\t\t\tvar data []rune\n\t\t\tif !o.cfg.UniqueEditLine {\n\t\t\t\to.buf.WriteRune('\\n')\n\t\t\t\tdata = o.buf.Reset()\n\t\t\t\tdata = data[:len(data)-1] // trim \\n\n\t\t\t} else {\n\t\t\t\to.buf.Clean()\n\t\t\t\tdata = o.buf.Reset()\n\t\t\t}\n\t\t\to.outchan <- data\n\t\t\tif !o.cfg.DisableAutoSaveHistory {\n\t\t\t\t// ignore IO error\n\t\t\t\t_ = o.history.New(data)\n\t\t\t} else {\n\t\t\t\tisUpdateHistory = false\n\t\t\t}\n\t\tcase CharBackward:\n\t\t\to.buf.MoveBackward()\n\t\tcase CharForward:\n\t\t\to.buf.MoveForward()\n\t\tcase CharPrev:\n\t\t\tbuf := o.history.Prev()\n\t\t\tif buf != nil {\n\t\t\t\to.buf.Set(buf)\n\t\t\t} else {\n\t\t\t\to.t.Bell()\n\t\t\t}\n\t\tcase CharNext:\n\t\t\tbuf, ok := o.history.Next()\n\t\t\tif ok {\n\t\t\t\to.buf.Set(buf)\n\t\t\t} else {\n\t\t\t\to.t.Bell()\n\t\t\t}\n\t\tcase CharDelete:\n\t\t\tif o.buf.Len() > 0 || !o.IsNormalMode() {\n\t\t\t\to.t.KickRead()\n\t\t\t\tif !o.buf.Delete() {\n\t\t\t\t\to.t.Bell()\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// treat as EOF\n\t\t\tif !o.cfg.UniqueEditLine {\n\t\t\t\to.buf.WriteString(o.cfg.EOFPrompt + \"\\n\")\n\t\t\t}\n\t\t\to.buf.Reset()\n\t\t\tisUpdateHistory = false\n\t\t\to.history.Revert()\n\t\t\to.errchan <- io.EOF\n\t\t\tif o.cfg.UniqueEditLine {\n\t\t\t\to.buf.Clean()\n\t\t\t}\n\t\tcase CharInterrupt:\n\t\t\tif o.IsInCompleteMode() {\n\t\t\t\to.t.KickRead()\n\t\t\t\to.ExitCompleteMode(true)\n\t\t\t\to.buf.Refresh(nil)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\to.buf.MoveToLineEnd()\n\t\t\to.buf.Refresh(nil)\n\t\t\thint := o.cfg.InterruptPrompt + \"\\n\"\n\t\t\tif !o.cfg.UniqueEditLine {\n\t\t\t\to.buf.WriteString(hint)\n\t\t\t}\n\t\t\tremain := o.buf.Reset()\n\t\t\tif !o.cfg.UniqueEditLine {\n\t\t\t\tremain = remain[:len(remain)-len([]rune(hint))]\n\t\t\t}\n\t\t\tisUpdateHistory = false\n\t\t\to.history.Revert()\n\t\t\to.errchan <- &InterruptError{remain}\n\t\tdefault:\n\t\t\to.buf.WriteRune(r)\n\t\t\tif o.IsInCompleteMode() {\n\t\t\t\to.OnComplete()\n\t\t\t\tkeepInCompleteMode = true\n\t\t\t}\n\t\t}\n\n\t\tif o.cfg.Listener != nil {\n\t\t\tnewLine, newPos, ok := o.cfg.Listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)\n\t\t\tif ok {\n\t\t\t\to.buf.SetWithIdx(newPos, newLine)\n\t\t\t}\n\t\t}\n\n\t\tif o.IsInCompleteMode() {\n\t\t\tif !keepInCompleteMode {\n\t\t\t\to.ExitCompleteMode(false)\n\t\t\t\to.Refresh()\n\t\t\t} else {\n\t\t\t\to.buf.Refresh(nil)\n\t\t\t\to.CompleteRefresh()\n\t\t\t}\n\t\t}\n\t\tif isUpdateHistory {\n\t\t\t// it will cause null history\n\t\t\to.history.Update(o.buf.Runes(), false)\n\t\t}\n\t}\n}\n\nfunc (o *Operation) Stderr() io.Writer {\n\treturn &wrapWriter{target: o.cfg.Stderr, r: o, t: o.t}\n}\n\nfunc (o *Operation) Stdout() io.Writer {\n\treturn &wrapWriter{target: o.cfg.Stdout, r: o, t: o.t}\n}\n\nfunc (o *Operation) String() (string, error) {\n\tr, err := o.Runes()\n\treturn string(r), err\n}\n\nfunc (o *Operation) Runes() ([]rune, error) {\n\to.t.EnterRawMode()\n\tdefer o.t.ExitRawMode()\n\n\tif o.cfg.Listener != nil {\n\t\to.cfg.Listener.OnChange(nil, 0, 0)\n\t}\n\n\to.buf.Refresh(nil) // print prompt\n\to.t.KickRead()\n\tselect {\n\tcase r := <-o.outchan:\n\t\treturn r, nil\n\tcase err := <-o.errchan:\n\t\tif e, ok := err.(*InterruptError); ok {\n\t\t\treturn e.Line, ErrInterrupt\n\t\t}\n\t\treturn nil, err\n\t}\n}\n\nfunc (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {\n\tcfg := o.GenPasswordConfig()\n\tcfg.Prompt = prompt\n\tcfg.Listener = l\n\treturn o.PasswordWithConfig(cfg)\n}\n\nfunc (o *Operation) GenPasswordConfig() *Config {\n\treturn o.opPassword.PasswordConfig()\n}\n\nfunc (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {\n\tif err := o.opPassword.EnterPasswordMode(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tdefer o.opPassword.ExitPasswordMode()\n\treturn o.Slice()\n}\n\nfunc (o *Operation) Password(prompt string) ([]byte, error) {\n\treturn o.PasswordEx(prompt, nil)\n}\n\nfunc (o *Operation) SetTitle(t string) {\n\to.w.Write([]byte(\"\\033[2;\" + t + \"\\007\"))\n}\n\nfunc (o *Operation) Slice() ([]byte, error) {\n\tr, err := o.Runes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []byte(string(r)), nil\n}\n\nfunc (o *Operation) Close() {\n\to.history.Close()\n}\n\nfunc (o *Operation) SetHistoryPath(path string) {\n\tif o.history != nil {\n\t\to.history.Close()\n\t}\n\to.cfg.HistoryFile = path\n\to.history = newOpHistory(o.cfg)\n}\n\nfunc (o *Operation) IsNormalMode() bool {\n\treturn !o.IsInCompleteMode()\n}\n\nfunc (op *Operation) SetConfig(cfg *Config) (*Config, error) {\n\tif op.cfg == cfg {\n\t\treturn op.cfg, nil\n\t}\n\tif err := cfg.Init(); err != nil {\n\t\treturn op.cfg, err\n\t}\n\told := op.cfg\n\top.cfg = cfg\n\top.SetPrompt(cfg.Prompt)\n\top.SetMaskRune(cfg.MaskRune)\n\top.buf.SetConfig(cfg)\n\twidth := op.cfg.FuncGetWidth()\n\n\tif cfg.opHistory == nil {\n\t\top.SetHistoryPath(cfg.HistoryFile)\n\t\tcfg.opHistory = op.history\n\t}\n\top.history = cfg.opHistory\n\n\t// SetHistoryPath will close opHistory which already exists\n\t// so if we use it next time, we need to reopen it by `InitHistory()`\n\top.history.Init()\n\n\tif op.cfg.AutoComplete != nil {\n\t\top.opCompleter = newOpCompleter(op.buf.w, op, width)\n\t}\n\n\treturn old, nil\n}\n\nfunc (o *Operation) ResetHistory() {\n\to.history.Reset()\n}\n\n// if err is not nil, it just mean it fail to write to file\n// other things goes fine.\nfunc (o *Operation) SaveHistory(content string) error {\n\treturn o.history.New([]rune(content))\n}\n\nfunc (o *Operation) Refresh() {\n\tif o.t.IsReading() {\n\t\to.buf.Refresh(nil)\n\t}\n}\n\nfunc (o *Operation) Clean() {\n\to.buf.Clean()\n}\n\nfunc FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {\n\treturn &DumpListener{f: f}\n}\n\ntype DumpListener struct {\n\tf func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)\n}\n\nfunc (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {\n\treturn d.f(line, pos, key)\n}\n\ntype Listener interface {\n\tOnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/password.go",
    "content": "package readline\n\ntype opPassword struct {\n\to         *Operation\n\tbackupCfg *Config\n}\n\nfunc newOpPassword(o *Operation) *opPassword {\n\treturn &opPassword{o: o}\n}\n\nfunc (o *opPassword) ExitPasswordMode() {\n\to.o.SetConfig(o.backupCfg)\n\to.backupCfg = nil\n}\n\nfunc (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {\n\to.backupCfg, err = o.o.SetConfig(cfg)\n\treturn\n}\n\nfunc (o *opPassword) PasswordConfig() *Config {\n\treturn &Config{\n\t\tEnableMask:      true,\n\t\tInterruptPrompt: \"\\n\",\n\t\tEOFPrompt:       \"\\n\",\n\t\tHistoryLimit:    -1,\n\n\t\tStdout: o.o.cfg.Stdout,\n\t\tStderr: o.o.cfg.Stderr,\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/rawreader_windows.go",
    "content": "// +build windows\n\npackage readline\n\nimport \"unsafe\"\n\nconst (\n\tVK_CANCEL   = 0x03\n\tVK_BACK     = 0x08\n\tVK_TAB      = 0x09\n\tVK_RETURN   = 0x0D\n\tVK_SHIFT    = 0x10\n\tVK_CONTROL  = 0x11\n\tVK_MENU     = 0x12\n\tVK_ESCAPE   = 0x1B\n\tVK_LEFT     = 0x25\n\tVK_UP       = 0x26\n\tVK_RIGHT    = 0x27\n\tVK_DOWN     = 0x28\n\tVK_DELETE   = 0x2E\n\tVK_LSHIFT   = 0xA0\n\tVK_RSHIFT   = 0xA1\n\tVK_LCONTROL = 0xA2\n\tVK_RCONTROL = 0xA3\n)\n\n// RawReader translate input record to ANSI escape sequence.\n// To provides same behavior as unix terminal.\ntype RawReader struct {\n\tctrlKey bool\n\taltKey  bool\n}\n\nfunc NewRawReader() *RawReader {\n\tr := new(RawReader)\n\treturn r\n}\n\n// only process one action in one read\nfunc (r *RawReader) Read(buf []byte) (int, error) {\n\tir := new(_INPUT_RECORD)\n\tvar read int\n\tvar err error\nnext:\n\terr = kernel.ReadConsoleInputW(stdin,\n\t\tuintptr(unsafe.Pointer(ir)),\n\t\t1,\n\t\tuintptr(unsafe.Pointer(&read)),\n\t)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif ir.EventType != EVENT_KEY {\n\t\tgoto next\n\t}\n\tker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0]))\n\tif ker.bKeyDown == 0 { // keyup\n\t\tif r.ctrlKey || r.altKey {\n\t\t\tswitch ker.wVirtualKeyCode {\n\t\t\tcase VK_RCONTROL, VK_LCONTROL:\n\t\t\t\tr.ctrlKey = false\n\t\t\tcase VK_MENU: //alt\n\t\t\t\tr.altKey = false\n\t\t\t}\n\t\t}\n\t\tgoto next\n\t}\n\n\tif ker.unicodeChar == 0 {\n\t\tvar target rune\n\t\tswitch ker.wVirtualKeyCode {\n\t\tcase VK_RCONTROL, VK_LCONTROL:\n\t\t\tr.ctrlKey = true\n\t\tcase VK_MENU: //alt\n\t\t\tr.altKey = true\n\t\tcase VK_LEFT:\n\t\t\ttarget = CharBackward\n\t\tcase VK_RIGHT:\n\t\t\ttarget = CharForward\n\t\tcase VK_UP:\n\t\t\ttarget = CharPrev\n\t\tcase VK_DOWN:\n\t\t\ttarget = CharNext\n\t\t}\n\t\tif target != 0 {\n\t\t\treturn r.write(buf, target)\n\t\t}\n\t\tgoto next\n\t}\n\tchar := rune(ker.unicodeChar)\n\tif r.ctrlKey {\n\t\tswitch char {\n\t\tcase 'A':\n\t\t\tchar = CharLineStart\n\t\tcase 'E':\n\t\t\tchar = CharLineEnd\n\t\tcase 'R':\n\t\t\tchar = CharBckSearch\n\t\tcase 'S':\n\t\t\tchar = CharFwdSearch\n\t\t}\n\t} else if r.altKey {\n\t\tswitch char {\n\t\tcase VK_BACK:\n\t\t\tchar = CharBackspace\n\t\t}\n\t\treturn r.writeEsc(buf, char)\n\t}\n\treturn r.write(buf, char)\n}\n\nfunc (r *RawReader) writeEsc(b []byte, char rune) (int, error) {\n\tb[0] = '\\033'\n\tn := copy(b[1:], []byte(string(char)))\n\treturn n + 1, nil\n}\n\nfunc (r *RawReader) write(b []byte, char rune) (int, error) {\n\tn := copy(b, []byte(string(char)))\n\treturn n, nil\n}\n\nfunc (r *RawReader) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/readline.go",
    "content": "// Readline is a pure go implementation for GNU-Readline kind library.\n//\n// example:\n// \trl, err := readline.New(\"> \")\n// \tif err != nil {\n// \t\tpanic(err)\n// \t}\n// \tdefer rl.Close()\n//\n// \tfor {\n// \t\tline, err := rl.Readline()\n// \t\tif err != nil { // io.EOF\n// \t\t\tbreak\n// \t\t}\n// \t\tprintln(line)\n// \t}\n//\npackage readline\n\nimport \"io\"\n\ntype Instance struct {\n\tConfig    *Config\n\tTerminal  *Terminal\n\tOperation *Operation\n}\n\ntype Config struct {\n\t// prompt supports ANSI escape sequence, so we can color some characters even in windows\n\tPrompt string\n\n\t// readline will persist historys to file where HistoryFile specified\n\tHistoryFile string\n\t// specify the max length of historys, it's 500 by default, set it to -1 to disable history\n\tHistoryLimit           int\n\tDisableAutoSaveHistory bool\n\t// enable case-insensitive history searching\n\tHistorySearchFold bool\n\n\t// AutoCompleter will called once user press TAB\n\tAutoComplete AutoCompleter\n\n\t// Any key press will pass to Listener\n\t// NOTE: Listener will be triggered by (nil, 0, 0) immediately\n\tListener Listener\n\n\t// If VimMode is true, readline will in vim.insert mode by default\n\tVimMode bool\n\n\tInterruptPrompt string\n\tEOFPrompt       string\n\n\tFuncGetWidth func() int\n\n\tStdin  io.Reader\n\tStdout io.Writer\n\tStderr io.Writer\n\n\tEnableMask bool\n\tMaskRune   rune\n\n\t// erase the editing line after user submited it\n\t// it use in IM usually.\n\tUniqueEditLine bool\n\n\t// force use interactive even stdout is not a tty\n\tFuncIsTerminal      func() bool\n\tFuncMakeRaw         func() error\n\tFuncExitRaw         func() error\n\tFuncOnWidthChanged  func(func())\n\tForceUseInteractive bool\n\n\t// private fields\n\tinited    bool\n\topHistory *opHistory\n}\n\nfunc (c *Config) useInteractive() bool {\n\tif c.ForceUseInteractive {\n\t\treturn true\n\t}\n\treturn c.FuncIsTerminal()\n}\n\nfunc (c *Config) Init() error {\n\tif c.inited {\n\t\treturn nil\n\t}\n\tc.inited = true\n\tif c.Stdin == nil {\n\t\tc.Stdin = NewCancelableStdin(Stdin)\n\t}\n\tif c.Stdout == nil {\n\t\tc.Stdout = Stdout\n\t}\n\tif c.Stderr == nil {\n\t\tc.Stderr = Stderr\n\t}\n\tif c.HistoryLimit == 0 {\n\t\tc.HistoryLimit = 500\n\t}\n\n\tif c.InterruptPrompt == \"\" {\n\t\tc.InterruptPrompt = \"^C\"\n\t} else if c.InterruptPrompt == \"\\n\" {\n\t\tc.InterruptPrompt = \"\"\n\t}\n\tif c.EOFPrompt == \"\" {\n\t\tc.EOFPrompt = \"^D\"\n\t} else if c.EOFPrompt == \"\\n\" {\n\t\tc.EOFPrompt = \"\"\n\t}\n\n\tif c.AutoComplete == nil {\n\t\tc.AutoComplete = &TabCompleter{}\n\t}\n\tif c.FuncGetWidth == nil {\n\t\tc.FuncGetWidth = GetScreenWidth\n\t}\n\tif c.FuncIsTerminal == nil {\n\t\tc.FuncIsTerminal = DefaultIsTerminal\n\t}\n\trm := new(RawMode)\n\tif c.FuncMakeRaw == nil {\n\t\tc.FuncMakeRaw = rm.Enter\n\t}\n\tif c.FuncExitRaw == nil {\n\t\tc.FuncExitRaw = rm.Exit\n\t}\n\tif c.FuncOnWidthChanged == nil {\n\t\tc.FuncOnWidthChanged = DefaultOnWidthChanged\n\t}\n\n\treturn nil\n}\n\nfunc (c Config) Clone() *Config {\n\tc.opHistory = nil\n\treturn &c\n}\n\nfunc (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {\n\tc.Listener = FuncListener(f)\n}\n\nfunc NewEx(cfg *Config) (*Instance, error) {\n\tt, err := NewTerminal(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trl := t.Readline()\n\treturn &Instance{\n\t\tConfig:    cfg,\n\t\tTerminal:  t,\n\t\tOperation: rl,\n\t}, nil\n}\n\nfunc New(prompt string) (*Instance, error) {\n\treturn NewEx(&Config{Prompt: prompt})\n}\n\nfunc (i *Instance) ResetHistory() {\n\ti.Operation.ResetHistory()\n}\n\nfunc (i *Instance) SetPrompt(s string) {\n\ti.Operation.SetPrompt(s)\n}\n\nfunc (i *Instance) SetMaskRune(r rune) {\n\ti.Operation.SetMaskRune(r)\n}\n\n// change history persistence in runtime\nfunc (i *Instance) SetHistoryPath(p string) {\n\ti.Operation.SetHistoryPath(p)\n}\n\n// readline will refresh automatic when write through Stdout()\nfunc (i *Instance) Stdout() io.Writer {\n\treturn i.Operation.Stdout()\n}\n\n// readline will refresh automatic when write through Stdout()\nfunc (i *Instance) Stderr() io.Writer {\n\treturn i.Operation.Stderr()\n}\n\n// switch VimMode in runtime\nfunc (i *Instance) SetVimMode(on bool) {\n\ti.Operation.SetVimMode(on)\n}\n\nfunc (i *Instance) IsVimMode() bool {\n\treturn i.Operation.IsEnableVimMode()\n}\n\nfunc (i *Instance) GenPasswordConfig() *Config {\n\treturn i.Operation.GenPasswordConfig()\n}\n\n// we can generate a config by `i.GenPasswordConfig()`\nfunc (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) {\n\treturn i.Operation.PasswordWithConfig(cfg)\n}\n\nfunc (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) {\n\treturn i.Operation.PasswordEx(prompt, l)\n}\n\nfunc (i *Instance) ReadPassword(prompt string) ([]byte, error) {\n\treturn i.Operation.Password(prompt)\n}\n\ntype Result struct {\n\tLine  string\n\tError error\n}\n\nfunc (l *Result) CanContinue() bool {\n\treturn len(l.Line) != 0 && l.Error == ErrInterrupt\n}\n\nfunc (l *Result) CanBreak() bool {\n\treturn !l.CanContinue() && l.Error != nil\n}\n\nfunc (i *Instance) Line() *Result {\n\tret, err := i.Readline()\n\treturn &Result{ret, err}\n}\n\n// err is one of (nil, io.EOF, readline.ErrInterrupt)\nfunc (i *Instance) Readline() (string, error) {\n\treturn i.Operation.String()\n}\n\nfunc (i *Instance) SaveHistory(content string) error {\n\treturn i.Operation.SaveHistory(content)\n}\n\n// same as readline\nfunc (i *Instance) ReadSlice() ([]byte, error) {\n\treturn i.Operation.Slice()\n}\n\n// we must make sure that call Close() before process exit.\nfunc (i *Instance) Close() error {\n\tif err := i.Terminal.Close(); err != nil {\n\t\treturn err\n\t}\n\ti.Operation.Close()\n\treturn nil\n}\nfunc (i *Instance) Clean() {\n\ti.Operation.Clean()\n}\n\nfunc (i *Instance) Write(b []byte) (int, error) {\n\treturn i.Stdout().Write(b)\n}\n\nfunc (i *Instance) SetConfig(cfg *Config) *Config {\n\tif i.Config == cfg {\n\t\treturn cfg\n\t}\n\told := i.Config\n\ti.Config = cfg\n\ti.Operation.SetConfig(cfg)\n\ti.Terminal.SetConfig(cfg)\n\treturn old\n}\n\nfunc (i *Instance) Refresh() {\n\ti.Operation.Refresh()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/readline_test.go",
    "content": "package readline\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRace(t *testing.T) {\n\trl, err := NewEx(&Config{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tgo func() {\n\t\tfor range time.Tick(time.Millisecond) {\n\t\t\trl.SetPrompt(\"hello\")\n\t\t}\n\t}()\n\n\tgo func() {\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\trl.Close()\n\t}()\n\n\trl.Readline()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/remote.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\ntype MsgType int16\n\nconst (\n\tT_DATA = MsgType(iota)\n\tT_WIDTH\n\tT_WIDTH_REPORT\n\tT_ISTTY_REPORT\n\tT_RAW\n\tT_ERAW // exit raw\n\tT_EOF\n)\n\ntype RemoteSvr struct {\n\teof           int32\n\tclosed        int32\n\twidth         int32\n\treciveChan    chan struct{}\n\twriteChan     chan *writeCtx\n\tconn          net.Conn\n\tisTerminal    bool\n\tfuncWidthChan func()\n\tstopChan      chan struct{}\n\n\tdataBufM sync.Mutex\n\tdataBuf  bytes.Buffer\n}\n\ntype writeReply struct {\n\tn   int\n\terr error\n}\n\ntype writeCtx struct {\n\tmsg   *Message\n\treply chan *writeReply\n}\n\nfunc newWriteCtx(msg *Message) *writeCtx {\n\treturn &writeCtx{\n\t\tmsg:   msg,\n\t\treply: make(chan *writeReply),\n\t}\n}\n\nfunc NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {\n\trs := &RemoteSvr{\n\t\twidth:      -1,\n\t\tconn:       conn,\n\t\twriteChan:  make(chan *writeCtx),\n\t\treciveChan: make(chan struct{}),\n\t\tstopChan:   make(chan struct{}),\n\t}\n\tbuf := bufio.NewReader(rs.conn)\n\n\tif err := rs.init(buf); err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo rs.readLoop(buf)\n\tgo rs.writeLoop()\n\treturn rs, nil\n}\n\nfunc (r *RemoteSvr) init(buf *bufio.Reader) error {\n\tm, err := ReadMessage(buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// receive isTerminal\n\tif m.Type != T_ISTTY_REPORT {\n\t\treturn fmt.Errorf(\"unexpected init message\")\n\t}\n\tr.GotIsTerminal(m.Data)\n\n\t// receive width\n\tm, err = ReadMessage(buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif m.Type != T_WIDTH_REPORT {\n\t\treturn fmt.Errorf(\"unexpected init message\")\n\t}\n\tr.GotReportWidth(m.Data)\n\n\treturn nil\n}\n\nfunc (r *RemoteSvr) HandleConfig(cfg *Config) {\n\tcfg.Stderr = r\n\tcfg.Stdout = r\n\tcfg.Stdin = r\n\tcfg.FuncExitRaw = r.ExitRawMode\n\tcfg.FuncIsTerminal = r.IsTerminal\n\tcfg.FuncMakeRaw = r.EnterRawMode\n\tcfg.FuncExitRaw = r.ExitRawMode\n\tcfg.FuncGetWidth = r.GetWidth\n\tcfg.FuncOnWidthChanged = func(f func()) {\n\t\tr.funcWidthChan = f\n\t}\n}\n\nfunc (r *RemoteSvr) IsTerminal() bool {\n\treturn r.isTerminal\n}\n\nfunc (r *RemoteSvr) checkEOF() error {\n\tif atomic.LoadInt32(&r.eof) == 1 {\n\t\treturn io.EOF\n\t}\n\treturn nil\n}\n\nfunc (r *RemoteSvr) Read(b []byte) (int, error) {\n\tr.dataBufM.Lock()\n\tn, err := r.dataBuf.Read(b)\n\tr.dataBufM.Unlock()\n\tif n == 0 {\n\t\tif err := r.checkEOF(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tif n == 0 && err == io.EOF {\n\t\t<-r.reciveChan\n\t\tr.dataBufM.Lock()\n\t\tn, err = r.dataBuf.Read(b)\n\t\tr.dataBufM.Unlock()\n\t}\n\tif n == 0 {\n\t\tif err := r.checkEOF(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\treturn n, err\n}\n\nfunc (r *RemoteSvr) writeMsg(m *Message) error {\n\tctx := newWriteCtx(m)\n\tr.writeChan <- ctx\n\treply := <-ctx.reply\n\treturn reply.err\n}\n\nfunc (r *RemoteSvr) Write(b []byte) (int, error) {\n\tctx := newWriteCtx(NewMessage(T_DATA, b))\n\tr.writeChan <- ctx\n\treply := <-ctx.reply\n\treturn reply.n, reply.err\n}\n\nfunc (r *RemoteSvr) EnterRawMode() error {\n\treturn r.writeMsg(NewMessage(T_RAW, nil))\n}\n\nfunc (r *RemoteSvr) ExitRawMode() error {\n\treturn r.writeMsg(NewMessage(T_ERAW, nil))\n}\n\nfunc (r *RemoteSvr) writeLoop() {\n\tdefer r.Close()\n\nloop:\n\tfor {\n\t\tselect {\n\t\tcase ctx, ok := <-r.writeChan:\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tn, err := ctx.msg.WriteTo(r.conn)\n\t\t\tctx.reply <- &writeReply{n, err}\n\t\tcase <-r.stopChan:\n\t\t\tbreak loop\n\t\t}\n\t}\n}\n\nfunc (r *RemoteSvr) Close() {\n\tif atomic.CompareAndSwapInt32(&r.closed, 0, 1) {\n\t\tclose(r.stopChan)\n\t\tr.conn.Close()\n\t}\n}\n\nfunc (r *RemoteSvr) readLoop(buf *bufio.Reader) {\n\tdefer r.Close()\n\tfor {\n\t\tm, err := ReadMessage(buf)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tswitch m.Type {\n\t\tcase T_EOF:\n\t\t\tatomic.StoreInt32(&r.eof, 1)\n\t\t\tselect {\n\t\t\tcase r.reciveChan <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\tcase T_DATA:\n\t\t\tr.dataBufM.Lock()\n\t\t\tr.dataBuf.Write(m.Data)\n\t\t\tr.dataBufM.Unlock()\n\t\t\tselect {\n\t\t\tcase r.reciveChan <- struct{}{}:\n\t\t\tdefault:\n\t\t\t}\n\t\tcase T_WIDTH_REPORT:\n\t\t\tr.GotReportWidth(m.Data)\n\t\tcase T_ISTTY_REPORT:\n\t\t\tr.GotIsTerminal(m.Data)\n\t\t}\n\t}\n}\n\nfunc (r *RemoteSvr) GotIsTerminal(data []byte) {\n\tif binary.BigEndian.Uint16(data) == 0 {\n\t\tr.isTerminal = false\n\t} else {\n\t\tr.isTerminal = true\n\t}\n}\n\nfunc (r *RemoteSvr) GotReportWidth(data []byte) {\n\tatomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))\n\tif r.funcWidthChan != nil {\n\t\tr.funcWidthChan()\n\t}\n}\n\nfunc (r *RemoteSvr) GetWidth() int {\n\treturn int(atomic.LoadInt32(&r.width))\n}\n\n// -----------------------------------------------------------------------------\n\ntype Message struct {\n\tType MsgType\n\tData []byte\n}\n\nfunc ReadMessage(r io.Reader) (*Message, error) {\n\tm := new(Message)\n\tvar length int32\n\tif err := binary.Read(r, binary.BigEndian, &length); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {\n\t\treturn nil, err\n\t}\n\tm.Data = make([]byte, int(length)-2)\n\tif _, err := io.ReadFull(r, m.Data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc NewMessage(t MsgType, data []byte) *Message {\n\treturn &Message{t, data}\n}\n\nfunc (m *Message) WriteTo(w io.Writer) (int, error) {\n\tbuf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))\n\tbinary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))\n\tbinary.Write(buf, binary.BigEndian, m.Type)\n\tbuf.Write(m.Data)\n\tn, err := buf.WriteTo(w)\n\treturn int(n), err\n}\n\n// -----------------------------------------------------------------------------\n\ntype RemoteCli struct {\n\tconn        net.Conn\n\traw         RawMode\n\treceiveChan chan struct{}\n\tinited      int32\n\tisTerminal  *bool\n\n\tdata  bytes.Buffer\n\tdataM sync.Mutex\n}\n\nfunc NewRemoteCli(conn net.Conn) (*RemoteCli, error) {\n\tr := &RemoteCli{\n\t\tconn:        conn,\n\t\treceiveChan: make(chan struct{}),\n\t}\n\treturn r, nil\n}\n\nfunc (r *RemoteCli) MarkIsTerminal(is bool) {\n\tr.isTerminal = &is\n}\n\nfunc (r *RemoteCli) init() error {\n\tif !atomic.CompareAndSwapInt32(&r.inited, 0, 1) {\n\t\treturn nil\n\t}\n\n\tif err := r.reportIsTerminal(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := r.reportWidth(); err != nil {\n\t\treturn err\n\t}\n\n\t// register sig for width changed\n\tDefaultOnWidthChanged(func() {\n\t\tr.reportWidth()\n\t})\n\treturn nil\n}\n\nfunc (r *RemoteCli) writeMsg(m *Message) error {\n\tr.dataM.Lock()\n\t_, err := m.WriteTo(r.conn)\n\tr.dataM.Unlock()\n\treturn err\n}\n\nfunc (r *RemoteCli) Write(b []byte) (int, error) {\n\tm := NewMessage(T_DATA, b)\n\tr.dataM.Lock()\n\t_, err := m.WriteTo(r.conn)\n\tr.dataM.Unlock()\n\treturn len(b), err\n}\n\nfunc (r *RemoteCli) reportWidth() error {\n\tscreenWidth := GetScreenWidth()\n\tdata := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(data, uint16(screenWidth))\n\tmsg := NewMessage(T_WIDTH_REPORT, data)\n\n\tif err := r.writeMsg(msg); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r *RemoteCli) reportIsTerminal() error {\n\tvar isTerminal bool\n\tif r.isTerminal != nil {\n\t\tisTerminal = *r.isTerminal\n\t} else {\n\t\tisTerminal = DefaultIsTerminal()\n\t}\n\tdata := make([]byte, 2)\n\tif isTerminal {\n\t\tbinary.BigEndian.PutUint16(data, 1)\n\t} else {\n\t\tbinary.BigEndian.PutUint16(data, 0)\n\t}\n\tmsg := NewMessage(T_ISTTY_REPORT, data)\n\tif err := r.writeMsg(msg); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r *RemoteCli) readLoop() {\n\tbuf := bufio.NewReader(r.conn)\n\tfor {\n\t\tmsg, err := ReadMessage(buf)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tswitch msg.Type {\n\t\tcase T_ERAW:\n\t\t\tr.raw.Exit()\n\t\tcase T_RAW:\n\t\t\tr.raw.Enter()\n\t\tcase T_DATA:\n\t\t\tos.Stdout.Write(msg.Data)\n\t\t}\n\t}\n}\n\nfunc (r *RemoteCli) ServeBy(source io.Reader) error {\n\tif err := r.init(); err != nil {\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\tdefer r.Close()\n\t\tfor {\n\t\t\tn, _ := io.Copy(r, source)\n\t\t\tif n == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\tdefer r.raw.Exit()\n\tr.readLoop()\n\treturn nil\n}\n\nfunc (r *RemoteCli) Close() {\n\tr.writeMsg(NewMessage(T_EOF, nil))\n}\n\nfunc (r *RemoteCli) Serve() error {\n\treturn r.ServeBy(os.Stdin)\n}\n\nfunc ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error {\n\tln, err := net.Listen(n, addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(onListen) > 0 {\n\t\tif err := onListen[0](ln); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tgo func() {\n\t\t\tdefer conn.Close()\n\t\t\trl, err := HandleConn(*cfg, conn)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\th(rl)\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc HandleConn(cfg Config, conn net.Conn) (*Instance, error) {\n\tr, err := NewRemoteSvr(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tr.HandleConfig(&cfg)\n\n\trl, err := NewEx(&cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn rl, nil\n}\n\nfunc DialRemote(n, addr string) error {\n\tconn, err := net.Dial(n, addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tcli, err := NewRemoteCli(conn)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.Serve()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/runebuf.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n)\n\ntype runeBufferBck struct {\n\tbuf []rune\n\tidx int\n}\n\ntype RuneBuffer struct {\n\tbuf    []rune\n\tidx    int\n\tprompt []rune\n\tw      io.Writer\n\n\thadClean    bool\n\tinteractive bool\n\tcfg         *Config\n\n\twidth int\n\n\tbck *runeBufferBck\n\n\toffset string\n\n\tsync.Mutex\n}\n\nfunc (r *RuneBuffer) OnWidthChange(newWidth int) {\n\tr.Lock()\n\tr.width = newWidth\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) Backup() {\n\tr.Lock()\n\tr.bck = &runeBufferBck{r.buf, r.idx}\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) Restore() {\n\tr.Refresh(func() {\n\t\tif r.bck == nil {\n\t\t\treturn\n\t\t}\n\t\tr.buf = r.bck.buf\n\t\tr.idx = r.bck.idx\n\t})\n}\n\nfunc NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer {\n\trb := &RuneBuffer{\n\t\tw:           w,\n\t\tinteractive: cfg.useInteractive(),\n\t\tcfg:         cfg,\n\t\twidth:       width,\n\t}\n\trb.SetPrompt(prompt)\n\treturn rb\n}\n\nfunc (r *RuneBuffer) SetConfig(cfg *Config) {\n\tr.Lock()\n\tr.cfg = cfg\n\tr.interactive = cfg.useInteractive()\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) SetMask(m rune) {\n\tr.Lock()\n\tr.cfg.MaskRune = m\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) CurrentWidth(x int) int {\n\tr.Lock()\n\tdefer r.Unlock()\n\treturn runes.WidthAll(r.buf[:x])\n}\n\nfunc (r *RuneBuffer) PromptLen() int {\n\tr.Lock()\n\twidth := r.promptLen()\n\tr.Unlock()\n\treturn width\n}\n\nfunc (r *RuneBuffer) promptLen() int {\n\treturn runes.WidthAll(runes.ColorFilter(r.prompt))\n}\n\nfunc (r *RuneBuffer) RuneSlice(i int) []rune {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tif i > 0 {\n\t\trs := make([]rune, i)\n\t\tcopy(rs, r.buf[r.idx:r.idx+i])\n\t\treturn rs\n\t}\n\trs := make([]rune, -i)\n\tcopy(rs, r.buf[r.idx+i:r.idx])\n\treturn rs\n}\n\nfunc (r *RuneBuffer) Runes() []rune {\n\tr.Lock()\n\tnewr := make([]rune, len(r.buf))\n\tcopy(newr, r.buf)\n\tr.Unlock()\n\treturn newr\n}\n\nfunc (r *RuneBuffer) Pos() int {\n\tr.Lock()\n\tdefer r.Unlock()\n\treturn r.idx\n}\n\nfunc (r *RuneBuffer) Len() int {\n\tr.Lock()\n\tdefer r.Unlock()\n\treturn len(r.buf)\n}\n\nfunc (r *RuneBuffer) MoveToLineStart() {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\t\tr.idx = 0\n\t})\n}\n\nfunc (r *RuneBuffer) MoveBackward() {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\t\tr.idx--\n\t})\n}\n\nfunc (r *RuneBuffer) WriteString(s string) {\n\tr.WriteRunes([]rune(s))\n}\n\nfunc (r *RuneBuffer) WriteRune(s rune) {\n\tr.WriteRunes([]rune{s})\n}\n\nfunc (r *RuneBuffer) WriteRunes(s []rune) {\n\tr.Refresh(func() {\n\t\ttail := append(s, r.buf[r.idx:]...)\n\t\tr.buf = append(r.buf[:r.idx], tail...)\n\t\tr.idx += len(s)\n\t})\n}\n\nfunc (r *RuneBuffer) MoveForward() {\n\tr.Refresh(func() {\n\t\tif r.idx == len(r.buf) {\n\t\t\treturn\n\t\t}\n\t\tr.idx++\n\t})\n}\n\nfunc (r *RuneBuffer) IsCursorInEnd() bool {\n\tr.Lock()\n\tdefer r.Unlock()\n\treturn r.idx == len(r.buf)\n}\n\nfunc (r *RuneBuffer) Replace(ch rune) {\n\tr.Refresh(func() {\n\t\tr.buf[r.idx] = ch\n\t})\n}\n\nfunc (r *RuneBuffer) Erase() {\n\tr.Refresh(func() {\n\t\tr.idx = 0\n\t\tr.buf = r.buf[:0]\n\t})\n}\n\nfunc (r *RuneBuffer) Delete() (success bool) {\n\tr.Refresh(func() {\n\t\tif r.idx == len(r.buf) {\n\t\t\treturn\n\t\t}\n\t\tr.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)\n\t\tsuccess = true\n\t})\n\treturn\n}\n\nfunc (r *RuneBuffer) DeleteWord() {\n\tif r.idx == len(r.buf) {\n\t\treturn\n\t}\n\tinit := r.idx\n\tfor init < len(r.buf) && IsWordBreak(r.buf[init]) {\n\t\tinit++\n\t}\n\tfor i := init + 1; i < len(r.buf); i++ {\n\t\tif !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {\n\t\t\tr.Refresh(func() {\n\t\t\t\tr.buf = append(r.buf[:r.idx], r.buf[i-1:]...)\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t}\n\tr.Kill()\n}\n\nfunc (r *RuneBuffer) MoveToPrevWord() (success bool) {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\n\t\tfor i := r.idx - 1; i > 0; i-- {\n\t\t\tif !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {\n\t\t\t\tr.idx = i\n\t\t\t\tsuccess = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tr.idx = 0\n\t\tsuccess = true\n\t})\n\treturn\n}\n\nfunc (r *RuneBuffer) KillFront() {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\n\t\tlength := len(r.buf) - r.idx\n\t\tcopy(r.buf[:length], r.buf[r.idx:])\n\t\tr.idx = 0\n\t\tr.buf = r.buf[:length]\n\t})\n}\n\nfunc (r *RuneBuffer) Kill() {\n\tr.Refresh(func() {\n\t\tr.buf = r.buf[:r.idx]\n\t})\n}\n\nfunc (r *RuneBuffer) Transpose() {\n\tr.Refresh(func() {\n\t\tif len(r.buf) == 1 {\n\t\t\tr.idx++\n\t\t}\n\n\t\tif len(r.buf) < 2 {\n\t\t\treturn\n\t\t}\n\n\t\tif r.idx == 0 {\n\t\t\tr.idx = 1\n\t\t} else if r.idx >= len(r.buf) {\n\t\t\tr.idx = len(r.buf) - 1\n\t\t}\n\t\tr.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx]\n\t\tr.idx++\n\t})\n}\n\nfunc (r *RuneBuffer) MoveToNextWord() {\n\tr.Refresh(func() {\n\t\tfor i := r.idx + 1; i < len(r.buf); i++ {\n\t\t\tif !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {\n\t\t\t\tr.idx = i\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tr.idx = len(r.buf)\n\t})\n}\n\nfunc (r *RuneBuffer) MoveToEndWord() {\n\tr.Refresh(func() {\n\t\t// already at the end, so do nothing\n\t\tif r.idx == len(r.buf) {\n\t\t\treturn\n\t\t}\n\t\t// if we are at the end of a word already, go to next\n\t\tif !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) {\n\t\t\tr.idx++\n\t\t}\n\n\t\t// keep going until at the end of a word\n\t\tfor i := r.idx + 1; i < len(r.buf); i++ {\n\t\t\tif IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) {\n\t\t\t\tr.idx = i - 1\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tr.idx = len(r.buf)\n\t})\n}\n\nfunc (r *RuneBuffer) BackEscapeWord() {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\t\tfor i := r.idx - 1; i > 0; i-- {\n\t\t\tif !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {\n\t\t\t\tr.buf = append(r.buf[:i], r.buf[r.idx:]...)\n\t\t\t\tr.idx = i\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tr.buf = r.buf[:0]\n\t\tr.idx = 0\n\t})\n}\n\nfunc (r *RuneBuffer) Backspace() {\n\tr.Refresh(func() {\n\t\tif r.idx == 0 {\n\t\t\treturn\n\t\t}\n\n\t\tr.idx--\n\t\tr.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)\n\t})\n}\n\nfunc (r *RuneBuffer) MoveToLineEnd() {\n\tr.Refresh(func() {\n\t\tif r.idx == len(r.buf) {\n\t\t\treturn\n\t\t}\n\n\t\tr.idx = len(r.buf)\n\t})\n}\n\nfunc (r *RuneBuffer) LineCount(width int) int {\n\tif width == -1 {\n\t\twidth = r.width\n\t}\n\treturn LineCount(width,\n\t\trunes.WidthAll(r.buf)+r.PromptLen())\n}\n\nfunc (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {\n\tr.Refresh(func() {\n\t\tif reverse {\n\t\t\tfor i := r.idx - 1; i >= 0; i-- {\n\t\t\t\tif r.buf[i] == ch {\n\t\t\t\t\tr.idx = i\n\t\t\t\t\tif prevChar {\n\t\t\t\t\t\tr.idx++\n\t\t\t\t\t}\n\t\t\t\t\tsuccess = true\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tfor i := r.idx + 1; i < len(r.buf); i++ {\n\t\t\tif r.buf[i] == ch {\n\t\t\t\tr.idx = i\n\t\t\t\tif prevChar {\n\t\t\t\t\tr.idx--\n\t\t\t\t}\n\t\t\t\tsuccess = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n\treturn\n}\n\nfunc (r *RuneBuffer) isInLineEdge() bool {\n\tif isWindows {\n\t\treturn false\n\t}\n\tsp := r.getSplitByLine(r.buf)\n\treturn len(sp[len(sp)-1]) == 0\n}\n\nfunc (r *RuneBuffer) getSplitByLine(rs []rune) []string {\n\treturn SplitByLine(r.promptLen(), r.width, rs)\n}\n\nfunc (r *RuneBuffer) IdxLine(width int) int {\n\tr.Lock()\n\tdefer r.Unlock()\n\treturn r.idxLine(width)\n}\n\nfunc (r *RuneBuffer) idxLine(width int) int {\n\tif width == 0 {\n\t\treturn 0\n\t}\n\tsp := r.getSplitByLine(r.buf[:r.idx])\n\treturn len(sp) - 1\n}\n\nfunc (r *RuneBuffer) CursorLineCount() int {\n\treturn r.LineCount(r.width) - r.IdxLine(r.width)\n}\n\nfunc (r *RuneBuffer) Refresh(f func()) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tif !r.interactive {\n\t\tif f != nil {\n\t\t\tf()\n\t\t}\n\t\treturn\n\t}\n\n\tr.clean()\n\tif f != nil {\n\t\tf()\n\t}\n\tr.print()\n}\n\nfunc (r *RuneBuffer) SetOffset(offset string) {\n\tr.Lock()\n\tr.offset = offset\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) print() {\n\tr.w.Write(r.output())\n\tr.hadClean = false\n}\n\nfunc (r *RuneBuffer) output() []byte {\n\tbuf := bytes.NewBuffer(nil)\n\tbuf.WriteString(string(r.prompt))\n\tif r.cfg.EnableMask && len(r.buf) > 0 {\n\t\tbuf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1)))\n\t\tif r.buf[len(r.buf)-1] == '\\n' {\n\t\t\tbuf.Write([]byte{'\\n'})\n\t\t} else {\n\t\t\tbuf.Write([]byte(string(r.cfg.MaskRune)))\n\t\t}\n\t\tif len(r.buf) > r.idx {\n\t\t\tbuf.Write(runes.Backspace(r.buf[r.idx:]))\n\t\t}\n\n\t} else {\n\t\tfor idx := range r.buf {\n\t\t\tif r.buf[idx] == '\\t' {\n\t\t\t\tbuf.WriteString(strings.Repeat(\" \", TabWidth))\n\t\t\t} else {\n\t\t\t\tbuf.WriteRune(r.buf[idx])\n\t\t\t}\n\t\t}\n\t\tif r.isInLineEdge() {\n\t\t\tbuf.Write([]byte(\" \\b\"))\n\t\t}\n\t}\n\n\tif len(r.buf) > r.idx {\n\t\tbuf.Write(runes.Backspace(r.buf[r.idx:]))\n\t}\n\treturn buf.Bytes()\n}\n\nfunc (r *RuneBuffer) Reset() []rune {\n\tret := runes.Copy(r.buf)\n\tr.buf = r.buf[:0]\n\tr.idx = 0\n\treturn ret\n}\n\nfunc (r *RuneBuffer) calWidth(m int) int {\n\tif m > 0 {\n\t\treturn runes.WidthAll(r.buf[r.idx : r.idx+m])\n\t}\n\treturn runes.WidthAll(r.buf[r.idx+m : r.idx])\n}\n\nfunc (r *RuneBuffer) SetStyle(start, end int, style string) {\n\tif end < start {\n\t\tpanic(\"end < start\")\n\t}\n\n\t// goto start\n\tmove := start - r.idx\n\tif move > 0 {\n\t\tr.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))\n\t} else {\n\t\tr.w.Write(bytes.Repeat([]byte(\"\\b\"), r.calWidth(move)))\n\t}\n\tr.w.Write([]byte(\"\\033[\" + style + \"m\"))\n\tr.w.Write([]byte(string(r.buf[start:end])))\n\tr.w.Write([]byte(\"\\033[0m\"))\n\t// TODO: move back\n}\n\nfunc (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {\n\tr.Refresh(func() {\n\t\tr.buf = buf\n\t\tr.idx = idx\n\t})\n}\n\nfunc (r *RuneBuffer) Set(buf []rune) {\n\tr.SetWithIdx(len(buf), buf)\n}\n\nfunc (r *RuneBuffer) SetPrompt(prompt string) {\n\tr.Lock()\n\tr.prompt = []rune(prompt)\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {\n\tbuf := bufio.NewWriter(w)\n\n\tif r.width == 0 {\n\t\tbuf.WriteString(strings.Repeat(\"\\r\\b\", len(r.buf)+r.promptLen()))\n\t\tbuf.Write([]byte(\"\\033[J\"))\n\t} else {\n\t\tbuf.Write([]byte(\"\\033[J\")) // just like ^k :)\n\t\tif idxLine == 0 {\n\t\t\tbuf.WriteString(\"\\033[2K\")\n\t\t\tbuf.WriteString(\"\\r\")\n\t\t} else {\n\t\t\tfor i := 0; i < idxLine; i++ {\n\t\t\t\tio.WriteString(buf, \"\\033[2K\\r\\033[A\")\n\t\t\t}\n\t\t\tio.WriteString(buf, \"\\033[2K\\r\")\n\t\t}\n\t}\n\tbuf.Flush()\n\treturn\n}\n\nfunc (r *RuneBuffer) Clean() {\n\tr.Lock()\n\tr.clean()\n\tr.Unlock()\n}\n\nfunc (r *RuneBuffer) clean() {\n\tr.cleanWithIdxLine(r.idxLine(r.width))\n}\n\nfunc (r *RuneBuffer) cleanWithIdxLine(idxLine int) {\n\tif r.hadClean || !r.interactive {\n\t\treturn\n\t}\n\tr.hadClean = true\n\tr.cleanOutput(r.w, idxLine)\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/runes/runes.go",
    "content": "// deprecated.\n// see https://github.com/chzyer/readline/issues/43\n// use github.com/chzyer/readline/runes.go\npackage runes\n\nimport (\n\t\"bytes\"\n\t\"unicode\"\n)\n\nfunc Equal(a, b []rune) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Search in runes from end to front\nfunc IndexAllBck(r, sub []rune) int {\n\tfor i := len(r) - len(sub); i >= 0; i-- {\n\t\tfound := true\n\t\tfor j := 0; j < len(sub); j++ {\n\t\t\tif r[i+j] != sub[j] {\n\t\t\t\tfound = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// Search in runes from front to end\nfunc IndexAll(r, sub []rune) int {\n\tfor i := 0; i < len(r); i++ {\n\t\tfound := true\n\t\tif len(r[i:]) < len(sub) {\n\t\t\treturn -1\n\t\t}\n\t\tfor j := 0; j < len(sub); j++ {\n\t\t\tif r[i+j] != sub[j] {\n\t\t\t\tfound = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc Index(r rune, rs []rune) int {\n\tfor i := 0; i < len(rs); i++ {\n\t\tif rs[i] == r {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc ColorFilter(r []rune) []rune {\n\tnewr := make([]rune, 0, len(r))\n\tfor pos := 0; pos < len(r); pos++ {\n\t\tif r[pos] == '\\033' && r[pos+1] == '[' {\n\t\t\tidx := Index('m', r[pos+2:])\n\t\t\tif idx == -1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpos += idx + 2\n\t\t\tcontinue\n\t\t}\n\t\tnewr = append(newr, r[pos])\n\t}\n\treturn newr\n}\n\nvar zeroWidth = []*unicode.RangeTable{\n\tunicode.Mn,\n\tunicode.Me,\n\tunicode.Cc,\n\tunicode.Cf,\n}\n\nvar doubleWidth = []*unicode.RangeTable{\n\tunicode.Han,\n\tunicode.Hangul,\n\tunicode.Hiragana,\n\tunicode.Katakana,\n}\n\nfunc Width(r rune) int {\n\tif unicode.IsOneOf(zeroWidth, r) {\n\t\treturn 0\n\t}\n\tif unicode.IsOneOf(doubleWidth, r) {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc WidthAll(r []rune) (length int) {\n\tfor i := 0; i < len(r); i++ {\n\t\tlength += Width(r[i])\n\t}\n\treturn\n}\n\nfunc Backspace(r []rune) []byte {\n\treturn bytes.Repeat([]byte{'\\b'}, WidthAll(r))\n}\n\nfunc Copy(r []rune) []rune {\n\tn := make([]rune, len(r))\n\tcopy(n, r)\n\treturn n\n}\n\nfunc HasPrefix(r, prefix []rune) bool {\n\tif len(r) < len(prefix) {\n\t\treturn false\n\t}\n\treturn Equal(r[:len(prefix)], prefix)\n}\n\nfunc Aggregate(candicate [][]rune) (same []rune, size int) {\n\tfor i := 0; i < len(candicate[0]); i++ {\n\t\tfor j := 0; j < len(candicate)-1; j++ {\n\t\t\tif i >= len(candicate[j]) || i >= len(candicate[j+1]) {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t\tif candicate[j][i] != candicate[j+1][i] {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t}\n\t\tsize = i + 1\n\t}\naggregate:\n\tif size > 0 {\n\t\tsame = Copy(candicate[0][:size])\n\t\tfor i := 0; i < len(candicate); i++ {\n\t\t\tn := Copy(candicate[i])\n\t\t\tcopy(n, n[size:])\n\t\t\tcandicate[i] = n[:len(n)-size]\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/runes/runes_test.go",
    "content": "package runes\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype twidth struct {\n\tr      []rune\n\tlength int\n}\n\nfunc TestRuneWidth(t *testing.T) {\n\trunes := []twidth{\n\t\t{[]rune(\"☭\"), 1},\n\t\t{[]rune(\"a\"), 1},\n\t\t{[]rune(\"你\"), 2},\n\t\t{ColorFilter([]rune(\"☭\\033[13;1m你\")), 3},\n\t}\n\tfor _, r := range runes {\n\t\tif w := WidthAll(r.r); w != r.length {\n\t\t\tt.Fatal(\"result not expect\", r.r, r.length, w)\n\t\t}\n\t}\n}\n\ntype tagg struct {\n\tr      [][]rune\n\te      [][]rune\n\tlength int\n}\n\nfunc TestAggRunes(t *testing.T) {\n\trunes := []tagg{\n\t\t{\n\t\t\t[][]rune{[]rune(\"ab\"), []rune(\"a\"), []rune(\"abc\")},\n\t\t\t[][]rune{[]rune(\"b\"), []rune(\"\"), []rune(\"bc\")},\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"addb\"), []rune(\"ajkajsdf\"), []rune(\"aasdfkc\")},\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"jkajsdf\"), []rune(\"asdfkc\")},\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ddajksdf\"), []rune(\"ddaasdfkc\")},\n\t\t\t[][]rune{[]rune(\"b\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t2,\n\t\t},\n\t}\n\tfor _, r := range runes {\n\t\tsame, off := Aggregate(r.r)\n\t\tif off != r.length {\n\t\t\tt.Fatal(\"result not expect\", off)\n\t\t}\n\t\tif len(same) != off {\n\t\t\tt.Fatal(\"result not expect\", same)\n\t\t}\n\t\tif !reflect.DeepEqual(r.r, r.e) {\n\t\t\tt.Fatal(\"result not expect\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/runes.go",
    "content": "package readline\n\nimport (\n\t\"bytes\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\nvar runes = Runes{}\nvar TabWidth = 4\n\ntype Runes struct{}\n\nfunc (Runes) EqualRune(a, b rune, fold bool) bool {\n\tif a == b {\n\t\treturn true\n\t}\n\tif !fold {\n\t\treturn false\n\t}\n\tif a > b {\n\t\ta, b = b, a\n\t}\n\tif b < utf8.RuneSelf && 'A' <= a && a <= 'Z' {\n\t\tif b == a+'a'-'A' {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (r Runes) EqualRuneFold(a, b rune) bool {\n\treturn r.EqualRune(a, b, true)\n}\n\nfunc (r Runes) EqualFold(a, b []rune) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif r.EqualRuneFold(a[i], b[i]) {\n\t\t\tcontinue\n\t\t}\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (Runes) Equal(a, b []rune) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := 0; i < len(a); i++ {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int {\n\tfor i := len(r) - len(sub); i >= 0; i-- {\n\t\tfound := true\n\t\tfor j := 0; j < len(sub); j++ {\n\t\t\tif !rs.EqualRune(r[i+j], sub[j], fold) {\n\t\t\t\tfound = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// Search in runes from end to front\nfunc (rs Runes) IndexAllBck(r, sub []rune) int {\n\treturn rs.IndexAllBckEx(r, sub, false)\n}\n\n// Search in runes from front to end\nfunc (rs Runes) IndexAll(r, sub []rune) int {\n\treturn rs.IndexAllEx(r, sub, false)\n}\n\nfunc (rs Runes) IndexAllEx(r, sub []rune, fold bool) int {\n\tfor i := 0; i < len(r); i++ {\n\t\tfound := true\n\t\tif len(r[i:]) < len(sub) {\n\t\t\treturn -1\n\t\t}\n\t\tfor j := 0; j < len(sub); j++ {\n\t\t\tif !rs.EqualRune(r[i+j], sub[j], fold) {\n\t\t\t\tfound = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif found {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (Runes) Index(r rune, rs []rune) int {\n\tfor i := 0; i < len(rs); i++ {\n\t\tif rs[i] == r {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (Runes) ColorFilter(r []rune) []rune {\n\tnewr := make([]rune, 0, len(r))\n\tfor pos := 0; pos < len(r); pos++ {\n\t\tif r[pos] == '\\033' && r[pos+1] == '[' {\n\t\t\tidx := runes.Index('m', r[pos+2:])\n\t\t\tif idx == -1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpos += idx + 2\n\t\t\tcontinue\n\t\t}\n\t\tnewr = append(newr, r[pos])\n\t}\n\treturn newr\n}\n\nvar zeroWidth = []*unicode.RangeTable{\n\tunicode.Mn,\n\tunicode.Me,\n\tunicode.Cc,\n\tunicode.Cf,\n}\n\nvar doubleWidth = []*unicode.RangeTable{\n\tunicode.Han,\n\tunicode.Hangul,\n\tunicode.Hiragana,\n\tunicode.Katakana,\n}\n\nfunc (Runes) Width(r rune) int {\n\tif r == '\\t' {\n\t\treturn TabWidth\n\t}\n\tif unicode.IsOneOf(zeroWidth, r) {\n\t\treturn 0\n\t}\n\tif unicode.IsOneOf(doubleWidth, r) {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (Runes) WidthAll(r []rune) (length int) {\n\tfor i := 0; i < len(r); i++ {\n\t\tlength += runes.Width(r[i])\n\t}\n\treturn\n}\n\nfunc (Runes) Backspace(r []rune) []byte {\n\treturn bytes.Repeat([]byte{'\\b'}, runes.WidthAll(r))\n}\n\nfunc (Runes) Copy(r []rune) []rune {\n\tn := make([]rune, len(r))\n\tcopy(n, r)\n\treturn n\n}\n\nfunc (Runes) HasPrefixFold(r, prefix []rune) bool {\n\tif len(r) < len(prefix) {\n\t\treturn false\n\t}\n\treturn runes.EqualFold(r[:len(prefix)], prefix)\n}\n\nfunc (Runes) HasPrefix(r, prefix []rune) bool {\n\tif len(r) < len(prefix) {\n\t\treturn false\n\t}\n\treturn runes.Equal(r[:len(prefix)], prefix)\n}\n\nfunc (Runes) Aggregate(candicate [][]rune) (same []rune, size int) {\n\tfor i := 0; i < len(candicate[0]); i++ {\n\t\tfor j := 0; j < len(candicate)-1; j++ {\n\t\t\tif i >= len(candicate[j]) || i >= len(candicate[j+1]) {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t\tif candicate[j][i] != candicate[j+1][i] {\n\t\t\t\tgoto aggregate\n\t\t\t}\n\t\t}\n\t\tsize = i + 1\n\t}\naggregate:\n\tif size > 0 {\n\t\tsame = runes.Copy(candicate[0][:size])\n\t\tfor i := 0; i < len(candicate); i++ {\n\t\t\tn := runes.Copy(candicate[i])\n\t\t\tcopy(n, n[size:])\n\t\t\tcandicate[i] = n[:len(n)-size]\n\t\t}\n\t}\n\treturn\n}\n\nfunc (Runes) TrimSpaceLeft(in []rune) []rune {\n\tfirstIndex := len(in)\n\tfor i, r := range in {\n\t\tif unicode.IsSpace(r) == false {\n\t\t\tfirstIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\treturn in[firstIndex:]\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/runes_test.go",
    "content": "package readline\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype twidth struct {\n\tr      []rune\n\tlength int\n}\n\nfunc TestRuneWidth(t *testing.T) {\n\trs := []twidth{\n\t\t{[]rune(\"☭\"), 1},\n\t\t{[]rune(\"a\"), 1},\n\t\t{[]rune(\"你\"), 2},\n\t\t{runes.ColorFilter([]rune(\"☭\\033[13;1m你\")), 3},\n\t}\n\tfor _, r := range rs {\n\t\tif w := runes.WidthAll(r.r); w != r.length {\n\t\t\tt.Fatal(\"result not expect\", r.r, r.length, w)\n\t\t}\n\t}\n}\n\ntype tagg struct {\n\tr      [][]rune\n\te      [][]rune\n\tlength int\n}\n\nfunc TestAggRunes(t *testing.T) {\n\trs := []tagg{\n\t\t{\n\t\t\t[][]rune{[]rune(\"ab\"), []rune(\"a\"), []rune(\"abc\")},\n\t\t\t[][]rune{[]rune(\"b\"), []rune(\"\"), []rune(\"bc\")},\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"addb\"), []rune(\"ajkajsdf\"), []rune(\"aasdfkc\")},\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"jkajsdf\"), []rune(\"asdfkc\")},\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t[][]rune{[]rune(\"ddb\"), []rune(\"ddajksdf\"), []rune(\"ddaasdfkc\")},\n\t\t\t[][]rune{[]rune(\"b\"), []rune(\"ajksdf\"), []rune(\"aasdfkc\")},\n\t\t\t2,\n\t\t},\n\t}\n\tfor _, r := range rs {\n\t\tsame, off := runes.Aggregate(r.r)\n\t\tif off != r.length {\n\t\t\tt.Fatal(\"result not expect\", off)\n\t\t}\n\t\tif len(same) != off {\n\t\t\tt.Fatal(\"result not expect\", same)\n\t\t}\n\t\tif !reflect.DeepEqual(r.r, r.e) {\n\t\t\tt.Fatal(\"result not expect\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/std.go",
    "content": "package readline\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n)\n\nvar (\n\tStdin  io.ReadCloser  = os.Stdin\n\tStdout io.WriteCloser = os.Stdout\n\tStderr io.WriteCloser = os.Stderr\n)\n\nvar (\n\tstd     *Instance\n\tstdOnce sync.Once\n)\n\n// global instance will not submit history automatic\nfunc getInstance() *Instance {\n\tstdOnce.Do(func() {\n\t\tstd, _ = NewEx(&Config{\n\t\t\tDisableAutoSaveHistory: true,\n\t\t})\n\t})\n\treturn std\n}\n\n// let readline load history from filepath\n// and try to persist history into disk\n// set fp to \"\" to prevent readline persisting history to disk\n// so the `AddHistory` will return nil error forever.\nfunc SetHistoryPath(fp string) {\n\tins := getInstance()\n\tcfg := ins.Config.Clone()\n\tcfg.HistoryFile = fp\n\tins.SetConfig(cfg)\n}\n\n// set auto completer to global instance\nfunc SetAutoComplete(completer AutoCompleter) {\n\tins := getInstance()\n\tcfg := ins.Config.Clone()\n\tcfg.AutoComplete = completer\n\tins.SetConfig(cfg)\n}\n\n// add history to global instance manually\n// raise error only if `SetHistoryPath` is set with a non-empty path\nfunc AddHistory(content string) error {\n\tins := getInstance()\n\treturn ins.SaveHistory(content)\n}\n\nfunc Password(prompt string) ([]byte, error) {\n\tins := getInstance()\n\treturn ins.ReadPassword(prompt)\n}\n\n// readline with global configs\nfunc Line(prompt string) (string, error) {\n\tins := getInstance()\n\tins.SetPrompt(prompt)\n\treturn ins.Readline()\n}\n\ntype CancelableStdin struct {\n\tr      io.Reader\n\tmutex  sync.Mutex\n\tstop   chan struct{}\n\tnotify chan struct{}\n\tdata   []byte\n\tread   int\n\terr    error\n}\n\nfunc NewCancelableStdin(r io.Reader) *CancelableStdin {\n\tc := &CancelableStdin{\n\t\tr:      r,\n\t\tnotify: make(chan struct{}),\n\t\tstop:   make(chan struct{}),\n\t}\n\tgo c.ioloop()\n\treturn c\n}\n\nfunc (c *CancelableStdin) ioloop() {\nloop:\n\tfor {\n\t\tselect {\n\t\tcase <-c.notify:\n\t\t\tc.read, c.err = c.r.Read(c.data)\n\t\t\tc.notify <- struct{}{}\n\t\tcase <-c.stop:\n\t\t\tbreak loop\n\t\t}\n\t}\n}\n\nfunc (c *CancelableStdin) Read(b []byte) (n int, err error) {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tc.data = b\n\tc.notify <- struct{}{}\n\tselect {\n\tcase <-c.notify:\n\t\treturn c.read, c.err\n\tcase <-c.stop:\n\t\treturn 0, io.EOF\n\t}\n}\n\nfunc (c *CancelableStdin) Close() error {\n\tclose(c.stop)\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/std_windows.go",
    "content": "// +build windows\n\npackage readline\n\nfunc init() {\n\tStdin = NewRawReader()\n\tStdout = NewANSIWriter(Stdout)\n\tStderr = NewANSIWriter(Stderr)\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/term.go",
    "content": "// Copyright 2011 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd\n\n// Package terminal provides support functions for dealing with terminals, as\n// commonly found on UNIX systems.\n//\n// Putting a terminal into raw mode is the most common requirement:\n//\n// \toldState, err := terminal.MakeRaw(0)\n// \tif err != nil {\n// \t        panic(err)\n// \t}\n// \tdefer terminal.Restore(0, oldState)\npackage readline\n\nimport (\n\t\"io\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// State contains the state of a terminal.\ntype State struct {\n\ttermios syscall.Termios\n}\n\n// IsTerminal returns true if the given file descriptor is a terminal.\nfunc IsTerminal(fd int) bool {\n\tvar termios syscall.Termios\n\t_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)\n\treturn err == 0\n}\n\n// MakeRaw put the terminal connected to the given file descriptor into raw\n// mode and returns the previous state of the terminal so that it can be\n// restored.\nfunc MakeRaw(fd int) (*State, error) {\n\tvar oldState State\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {\n\t\treturn nil, err\n\t}\n\n\tnewState := oldState.termios\n\t// This attempts to replicate the behaviour documented for cfmakeraw in\n\t// the termios(3) manpage.\n\tnewState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON\n\t// newState.Oflag &^= syscall.OPOST\n\tnewState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN\n\tnewState.Cflag &^= syscall.CSIZE | syscall.PARENB\n\tnewState.Cflag |= syscall.CS8\n\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {\n\t\treturn nil, err\n\t}\n\n\treturn &oldState, nil\n}\n\n// GetState returns the current state of a terminal which may be useful to\n// restore the terminal after a signal.\nfunc GetState(fd int) (*State, error) {\n\tvar oldState State\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {\n\t\treturn nil, err\n\t}\n\n\treturn &oldState, nil\n}\n\n// Restore restores the terminal connected to the given file descriptor to a\n// previous state.\nfunc restoreTerm(fd int, state *State) error {\n\t_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)\n\treturn err\n}\n\n// GetSize returns the dimensions of the given terminal.\nfunc GetSize(fd int) (width, height int, err error) {\n\tvar dimensions [4]uint16\n\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {\n\t\treturn -1, -1, err\n\t}\n\treturn int(dimensions[1]), int(dimensions[0]), nil\n}\n\n// ReadPassword reads a line of input from a terminal without local echo.  This\n// is commonly used for inputting passwords and other sensitive data. The slice\n// returned does not include the \\n.\nfunc ReadPassword(fd int) ([]byte, error) {\n\tvar oldState syscall.Termios\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {\n\t\treturn nil, err\n\t}\n\n\tnewState := oldState\n\tnewState.Lflag &^= syscall.ECHO\n\tnewState.Lflag |= syscall.ICANON | syscall.ISIG\n\tnewState.Iflag |= syscall.ICRNL\n\tif _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\tsyscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)\n\t}()\n\n\tvar buf [16]byte\n\tvar ret []byte\n\tfor {\n\t\tn, err := syscall.Read(fd, buf[:])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif n == 0 {\n\t\t\tif len(ret) == 0 {\n\t\t\t\treturn nil, io.EOF\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif buf[n-1] == '\\n' {\n\t\t\tn--\n\t\t}\n\t\tret = append(ret, buf[:n]...)\n\t\tif n < len(buf) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/term_bsd.go",
    "content": "// Copyright 2013 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// +build darwin dragonfly freebsd netbsd openbsd\n\npackage readline\n\nimport \"syscall\"\n\nconst ioctlReadTermios = syscall.TIOCGETA\nconst ioctlWriteTermios = syscall.TIOCSETA\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/term_linux.go",
    "content": "// Copyright 2013 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage readline\n\n// These constants are declared here, rather than importing\n// them from the syscall package as some syscall packages, even\n// on linux, for example gccgo, do not declare them.\nconst ioctlReadTermios = 0x5401  // syscall.TCGETS\nconst ioctlWriteTermios = 0x5402 // syscall.TCSETS\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/term_windows.go",
    "content": "// Copyright 2011 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// +build windows\n\n// Package terminal provides support functions for dealing with terminals, as\n// commonly found on UNIX systems.\n//\n// Putting a terminal into raw mode is the most common requirement:\n//\n// \toldState, err := terminal.MakeRaw(0)\n// \tif err != nil {\n// \t        panic(err)\n// \t}\n// \tdefer terminal.Restore(0, oldState)\npackage readline\n\nimport (\n\t\"io\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nconst (\n\tenableLineInput       = 2\n\tenableEchoInput       = 4\n\tenableProcessedInput  = 1\n\tenableWindowInput     = 8\n\tenableMouseInput      = 16\n\tenableInsertMode      = 32\n\tenableQuickEditMode   = 64\n\tenableExtendedFlags   = 128\n\tenableAutoPosition    = 256\n\tenableProcessedOutput = 1\n\tenableWrapAtEolOutput = 2\n)\n\nvar kernel32 = syscall.NewLazyDLL(\"kernel32.dll\")\n\nvar (\n\tprocGetConsoleMode             = kernel32.NewProc(\"GetConsoleMode\")\n\tprocSetConsoleMode             = kernel32.NewProc(\"SetConsoleMode\")\n\tprocGetConsoleScreenBufferInfo = kernel32.NewProc(\"GetConsoleScreenBufferInfo\")\n)\n\ntype (\n\tcoord struct {\n\t\tx short\n\t\ty short\n\t}\n\tsmallRect struct {\n\t\tleft   short\n\t\ttop    short\n\t\tright  short\n\t\tbottom short\n\t}\n\tconsoleScreenBufferInfo struct {\n\t\tsize              coord\n\t\tcursorPosition    coord\n\t\tattributes        word\n\t\twindow            smallRect\n\t\tmaximumWindowSize coord\n\t}\n)\n\ntype State struct {\n\tmode uint32\n}\n\n// IsTerminal returns true if the given file descriptor is a terminal.\nfunc IsTerminal(fd int) bool {\n\tvar st uint32\n\tr, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)\n\treturn r != 0 && e == 0\n}\n\n// MakeRaw put the terminal connected to the given file descriptor into raw\n// mode and returns the previous state of the terminal so that it can be\n// restored.\nfunc MakeRaw(fd int) (*State, error) {\n\tvar st uint32\n\t_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)\n\tif e != 0 {\n\t\treturn nil, error(e)\n\t}\n\traw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)\n\t_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)\n\tif e != 0 {\n\t\treturn nil, error(e)\n\t}\n\treturn &State{st}, nil\n}\n\n// GetState returns the current state of a terminal which may be useful to\n// restore the terminal after a signal.\nfunc GetState(fd int) (*State, error) {\n\tvar st uint32\n\t_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)\n\tif e != 0 {\n\t\treturn nil, error(e)\n\t}\n\treturn &State{st}, nil\n}\n\n// Restore restores the terminal connected to the given file descriptor to a\n// previous state.\nfunc restoreTerm(fd int, state *State) error {\n\t_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)\n\treturn err\n}\n\n// GetSize returns the dimensions of the given terminal.\nfunc GetSize(fd int) (width, height int, err error) {\n\tvar info consoleScreenBufferInfo\n\t_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)\n\tif e != 0 {\n\t\treturn 0, 0, error(e)\n\t}\n\treturn int(info.size.x), int(info.size.y), nil\n}\n\n// ReadPassword reads a line of input from a terminal without local echo.  This\n// is commonly used for inputting passwords and other sensitive data. The slice\n// returned does not include the \\n.\nfunc ReadPassword(fd int) ([]byte, error) {\n\tvar st uint32\n\t_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)\n\tif e != 0 {\n\t\treturn nil, error(e)\n\t}\n\told := st\n\n\tst &^= (enableEchoInput)\n\tst |= (enableProcessedInput | enableLineInput | enableProcessedOutput)\n\t_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)\n\tif e != 0 {\n\t\treturn nil, error(e)\n\t}\n\n\tdefer func() {\n\t\tsyscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)\n\t}()\n\n\tvar buf [16]byte\n\tvar ret []byte\n\tfor {\n\t\tn, err := syscall.Read(syscall.Handle(fd), buf[:])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif n == 0 {\n\t\t\tif len(ret) == 0 {\n\t\t\t\treturn nil, io.EOF\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif buf[n-1] == '\\n' {\n\t\t\tn--\n\t\t}\n\t\tif n > 0 && buf[n-1] == '\\r' {\n\t\t\tn--\n\t\t}\n\t\tret = append(ret, buf[:n]...)\n\t\tif n < len(buf) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/terminal.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype Terminal struct {\n\tcfg       *Config\n\toutchan   chan rune\n\tclosed    int32\n\tstopChan  chan struct{}\n\tkickChan  chan struct{}\n\twg        sync.WaitGroup\n\tisReading int32\n\tsleeping  int32\n\tpause     int32\n\n\tsizeChan chan string\n}\n\nfunc NewTerminal(cfg *Config) (*Terminal, error) {\n\tif err := cfg.Init(); err != nil {\n\t\treturn nil, err\n\t}\n\tt := &Terminal{\n\t\tcfg:      cfg,\n\t\tkickChan: make(chan struct{}, 1),\n\t\toutchan:  make(chan rune),\n\t\tstopChan: make(chan struct{}, 1),\n\t\tsizeChan: make(chan string, 1),\n\t}\n\n\tgo t.ioloop()\n\treturn t, nil\n}\n\n// SleepToResume will sleep myself, and return only if I'm resumed.\nfunc (t *Terminal) SleepToResume() {\n\tif !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {\n\t\treturn\n\t}\n\tdefer atomic.StoreInt32(&t.sleeping, 0)\n\n\tt.ExitRawMode()\n\tch := WaitForResume()\n\tSuspendMe()\n\t<-ch\n\tt.EnterRawMode()\n}\n\nfunc (t *Terminal) EnterRawMode() (err error) {\n\treturn t.cfg.FuncMakeRaw()\n}\n\nfunc (t *Terminal) ExitRawMode() (err error) {\n\treturn t.cfg.FuncExitRaw()\n}\n\nfunc (t *Terminal) Write(b []byte) (int, error) {\n\treturn t.cfg.Stdout.Write(b)\n}\n\ntype termSize struct {\n\tleft int\n\ttop  int\n}\n\nfunc (t *Terminal) GetOffset(f func(offset string)) {\n\tgo func() {\n\t\tf(<-t.sizeChan)\n\t}()\n\tt.Write([]byte(\"\\033[6n\"))\n}\n\nfunc (t *Terminal) Print(s string) {\n\tfmt.Fprintf(t.cfg.Stdout, \"%s\", s)\n}\n\nfunc (t *Terminal) PrintRune(r rune) {\n\tfmt.Fprintf(t.cfg.Stdout, \"%c\", r)\n}\n\nfunc (t *Terminal) Readline() *Operation {\n\treturn NewOperation(t, t.cfg)\n}\n\n// return rune(0) if meet EOF\nfunc (t *Terminal) ReadRune() rune {\n\tch, ok := <-t.outchan\n\tif !ok {\n\t\treturn rune(0)\n\t}\n\treturn ch\n}\n\nfunc (t *Terminal) IsReading() bool {\n\treturn atomic.LoadInt32(&t.isReading) == 1\n}\n\nfunc (t *Terminal) KickRead() {\n\tselect {\n\tcase t.kickChan <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (t *Terminal) PauseRead(b bool) {\n\tif b {\n\t\tatomic.StoreInt32(&t.pause, 1)\n\t} else {\n\t\tatomic.StoreInt32(&t.pause, 0)\n\t}\n}\n\nfunc (t *Terminal) ioloop() {\n\tt.wg.Add(1)\n\tdefer func() {\n\t\tt.wg.Done()\n\t\tclose(t.outchan)\n\t}()\n\n\tvar (\n\t\tisEscape       bool\n\t\tisEscapeEx     bool\n\t\texpectNextChar bool\n\t)\n\n\tbuf := bufio.NewReader(t.cfg.Stdin)\n\tfor {\n\t\tpause := atomic.LoadInt32(&t.pause)\n\n\t\tif pause == 1 {\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !expectNextChar {\n\t\t\tatomic.StoreInt32(&t.isReading, 0)\n\t\t\tselect {\n\t\t\tcase <-t.kickChan:\n\t\t\t\tatomic.StoreInt32(&t.isReading, 1)\n\t\t\tcase <-t.stopChan:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\texpectNextChar = false\n\t\tr, _, err := buf.ReadRune()\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"interrupted system call\") {\n\t\t\t\texpectNextChar = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tif isEscape {\n\t\t\tisEscape = false\n\t\t\tif r == CharEscapeEx {\n\t\t\t\texpectNextChar = true\n\t\t\t\tisEscapeEx = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr = escapeKey(r, buf)\n\t\t} else if isEscapeEx {\n\t\t\tisEscapeEx = false\n\t\t\tif key := readEscKey(r, buf); key != nil {\n\t\t\t\tr = escapeExKey(key)\n\t\t\t\t// offset\n\t\t\t\tif key.typ == 'R' {\n\t\t\t\t\tif _, _, ok := key.Get2(); ok {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase t.sizeChan <- key.attr:\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\texpectNextChar = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r == 0 {\n\t\t\t\texpectNextChar = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\texpectNextChar = true\n\t\tswitch r {\n\t\tcase CharEsc:\n\t\t\tif t.cfg.VimMode {\n\t\t\t\tt.outchan <- r\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tisEscape = true\n\t\tcase CharInterrupt, CharEnter, CharCtrlJ, CharDelete:\n\t\t\texpectNextChar = false\n\t\t\tfallthrough\n\t\tdefault:\n\t\t\tif r == CharTab {\n\t\t\t\tt.PauseRead(true)\n\t\t\t}\n\n\t\t\tt.outchan <- r\n\t\t}\n\t}\n\n}\n\nfunc (t *Terminal) Bell() {\n\tfmt.Fprintf(t, \"%c\", CharBell)\n}\n\nfunc (t *Terminal) Close() error {\n\tif atomic.SwapInt32(&t.closed, 1) != 0 {\n\t\treturn nil\n\t}\n\tif closer, ok := t.cfg.Stdin.(io.Closer); ok {\n\t\tcloser.Close()\n\t}\n\tclose(t.stopChan)\n\tt.wg.Wait()\n\treturn t.ExitRawMode()\n}\n\nfunc (t *Terminal) SetConfig(c *Config) error {\n\tif err := c.Init(); err != nil {\n\t\treturn err\n\t}\n\tt.cfg = c\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/utils.go",
    "content": "package readline\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"container/list\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unicode\"\n)\n\nvar (\n\tisWindows = false\n)\n\nconst (\n\tCharLineStart = 1\n\tCharBackward  = 2\n\tCharInterrupt = 3\n\tCharDelete    = 4\n\tCharLineEnd   = 5\n\tCharForward   = 6\n\tCharBell      = 7\n\tCharCtrlH     = 8\n\tCharTab       = 9\n\tCharCtrlJ     = 10\n\tCharKill      = 11\n\tCharCtrlL     = 12\n\tCharEnter     = 13\n\tCharNext      = 14\n\tCharPrev      = 16\n\tCharBckSearch = 18\n\tCharFwdSearch = 19\n\tCharTranspose = 20\n\tCharCtrlU     = 21\n\tCharCtrlW     = 23\n\tCharCtrlZ     = 26\n\tCharEsc       = 27\n\tCharEscapeEx  = 91\n\tCharBackspace = 127\n)\n\nconst (\n\tMetaBackward rune = -iota - 1\n\tMetaForward\n\tMetaDelete\n\tMetaBackspace\n\tMetaTranspose\n)\n\n// WaitForResume need to call before current process got suspend.\n// It will run a ticker until a long duration is occurs,\n// which means this process is resumed.\nfunc WaitForResume() chan struct{} {\n\tch := make(chan struct{})\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tticker := time.NewTicker(10 * time.Millisecond)\n\t\tt := time.Now()\n\t\twg.Done()\n\t\tfor {\n\t\t\tnow := <-ticker.C\n\t\t\tif now.Sub(t) > 100*time.Millisecond {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tt = now\n\t\t}\n\t\tticker.Stop()\n\t\tch <- struct{}{}\n\t}()\n\twg.Wait()\n\treturn ch\n}\n\nfunc Restore(fd int, state *State) error {\n\terr := restoreTerm(fd, state)\n\tif err != nil {\n\t\t// errno 0 means everything is ok :)\n\t\tif err.Error() == \"errno 0\" {\n\t\t\terr = nil\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc IsPrintable(key rune) bool {\n\tisInSurrogateArea := key >= 0xd800 && key <= 0xdbff\n\treturn key >= 32 && !isInSurrogateArea\n}\n\n// translate Esc[X\nfunc escapeExKey(key *escapeKeyPair) rune {\n\tvar r rune\n\tswitch key.typ {\n\tcase 'D':\n\t\tr = CharBackward\n\tcase 'C':\n\t\tr = CharForward\n\tcase 'A':\n\t\tr = CharPrev\n\tcase 'B':\n\t\tr = CharNext\n\tcase 'H':\n\t\tr = CharLineStart\n\tcase 'F':\n\t\tr = CharLineEnd\n\tcase '~':\n\t\tif key.attr == \"3\" {\n\t\t\tr = CharDelete\n\t\t}\n\tdefault:\n\t}\n\treturn r\n}\n\ntype escapeKeyPair struct {\n\tattr string\n\ttyp  rune\n}\n\nfunc (e *escapeKeyPair) Get2() (int, int, bool) {\n\tsp := strings.Split(e.attr, \";\")\n\tif len(sp) < 2 {\n\t\treturn -1, -1, false\n\t}\n\ts1, err := strconv.Atoi(sp[0])\n\tif err != nil {\n\t\treturn -1, -1, false\n\t}\n\ts2, err := strconv.Atoi(sp[1])\n\tif err != nil {\n\t\treturn -1, -1, false\n\t}\n\treturn s1, s2, true\n}\n\nfunc readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair {\n\tp := escapeKeyPair{}\n\tbuf := bytes.NewBuffer(nil)\n\tfor {\n\t\tif r == ';' {\n\t\t} else if unicode.IsNumber(r) {\n\t\t} else {\n\t\t\tp.typ = r\n\t\t\tbreak\n\t\t}\n\t\tbuf.WriteRune(r)\n\t\tr, _, _ = reader.ReadRune()\n\t}\n\tp.attr = buf.String()\n\treturn &p\n}\n\n// translate EscX to Meta+X\nfunc escapeKey(r rune, reader *bufio.Reader) rune {\n\tswitch r {\n\tcase 'b':\n\t\tr = MetaBackward\n\tcase 'f':\n\t\tr = MetaForward\n\tcase 'd':\n\t\tr = MetaDelete\n\tcase CharTranspose:\n\t\tr = MetaTranspose\n\tcase CharBackspace:\n\t\tr = MetaBackspace\n\tcase 'O':\n\t\td, _, _ := reader.ReadRune()\n\t\tswitch d {\n\t\tcase 'H':\n\t\t\tr = CharLineStart\n\t\tcase 'F':\n\t\t\tr = CharLineEnd\n\t\tdefault:\n\t\t\treader.UnreadRune()\n\t\t}\n\tcase CharEsc:\n\n\t}\n\treturn r\n}\n\nfunc SplitByLine(start, screenWidth int, rs []rune) []string {\n\tvar ret []string\n\tbuf := bytes.NewBuffer(nil)\n\tcurrentWidth := start\n\tfor _, r := range rs {\n\t\tw := runes.Width(r)\n\t\tcurrentWidth += w\n\t\tbuf.WriteRune(r)\n\t\tif currentWidth >= screenWidth {\n\t\t\tret = append(ret, buf.String())\n\t\t\tbuf.Reset()\n\t\t\tcurrentWidth = 0\n\t\t}\n\t}\n\tret = append(ret, buf.String())\n\treturn ret\n}\n\n// calculate how many lines for N character\nfunc LineCount(screenWidth, w int) int {\n\tr := w / screenWidth\n\tif w%screenWidth != 0 {\n\t\tr++\n\t}\n\treturn r\n}\n\nfunc IsWordBreak(i rune) bool {\n\tswitch {\n\tcase i >= 'a' && i <= 'z':\n\tcase i >= 'A' && i <= 'Z':\n\tcase i >= '0' && i <= '9':\n\tdefault:\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetInt(s []string, def int) int {\n\tif len(s) == 0 {\n\t\treturn def\n\t}\n\tc, err := strconv.Atoi(s[0])\n\tif err != nil {\n\t\treturn def\n\t}\n\treturn c\n}\n\ntype RawMode struct {\n\tstate *State\n}\n\nfunc (r *RawMode) Enter() (err error) {\n\tr.state, err = MakeRaw(GetStdin())\n\treturn err\n}\n\nfunc (r *RawMode) Exit() error {\n\tif r.state == nil {\n\t\treturn nil\n\t}\n\treturn Restore(GetStdin(), r.state)\n}\n\n// -----------------------------------------------------------------------------\n\nfunc sleep(n int) {\n\tDebug(n)\n\ttime.Sleep(2000 * time.Millisecond)\n}\n\n// print a linked list to Debug()\nfunc debugList(l *list.List) {\n\tidx := 0\n\tfor e := l.Front(); e != nil; e = e.Next() {\n\t\tDebug(idx, fmt.Sprintf(\"%+v\", e.Value))\n\t\tidx++\n\t}\n}\n\n// append log info to another file\nfunc Debug(o ...interface{}) {\n\tf, _ := os.OpenFile(\"debug.tmp\", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\tfmt.Fprintln(f, o...)\n\tf.Close()\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/utils_test.go",
    "content": "package readline\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/utils_unix.go",
    "content": "// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd\n\npackage readline\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\ntype winsize struct {\n\tRow    uint16\n\tCol    uint16\n\tXpixel uint16\n\tYpixel uint16\n}\n\n// SuspendMe use to send suspend signal to myself, when we in the raw mode.\n// For OSX it need to send to parent's pid\n// For Linux it need to send to myself\nfunc SuspendMe() {\n\tp, _ := os.FindProcess(os.Getppid())\n\tp.Signal(syscall.SIGTSTP)\n\tp, _ = os.FindProcess(os.Getpid())\n\tp.Signal(syscall.SIGTSTP)\n}\n\n// get width of the terminal\nfunc getWidth(stdoutFd int) int {\n\tws := &winsize{}\n\tretCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,\n\t\tuintptr(stdoutFd),\n\t\tuintptr(syscall.TIOCGWINSZ),\n\t\tuintptr(unsafe.Pointer(ws)))\n\n\tif int(retCode) == -1 {\n\t\t_ = errno\n\t\treturn -1\n\t}\n\treturn int(ws.Col)\n}\n\nfunc GetScreenWidth() int {\n\tw := getWidth(syscall.Stdout)\n\tif w < 0 {\n\t\tw = getWidth(syscall.Stderr)\n\t}\n\treturn w\n}\n\n// ClearScreen clears the console screen\nfunc ClearScreen(w io.Writer) (int, error) {\n\treturn w.Write([]byte(\"\\033[H\"))\n}\n\nfunc DefaultIsTerminal() bool {\n\treturn IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr))\n}\n\nfunc GetStdin() int {\n\treturn syscall.Stdin\n}\n\n// -----------------------------------------------------------------------------\n\nvar (\n\twidthChange         sync.Once\n\twidthChangeCallback func()\n)\n\nfunc DefaultOnWidthChanged(f func()) {\n\twidthChangeCallback = f\n\twidthChange.Do(func() {\n\t\tch := make(chan os.Signal, 1)\n\t\tsignal.Notify(ch, syscall.SIGWINCH)\n\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\t_, ok := <-ch\n\t\t\t\tif !ok {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\twidthChangeCallback()\n\t\t\t}\n\t\t}()\n\t})\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/utils_windows.go",
    "content": "// +build windows\n\npackage readline\n\nimport (\n\t\"io\"\n\t\"syscall\"\n)\n\nfunc SuspendMe() {\n}\n\nfunc GetStdin() int {\n\treturn int(syscall.Stdin)\n}\n\nfunc init() {\n\tisWindows = true\n}\n\n// get width of the terminal\nfunc GetScreenWidth() int {\n\tinfo, _ := GetConsoleScreenBufferInfo()\n\tif info == nil {\n\t\treturn -1\n\t}\n\treturn int(info.dwSize.x)\n}\n\n// ClearScreen clears the console screen\nfunc ClearScreen(_ io.Writer) error {\n\treturn SetConsoleCursorPosition(&_COORD{0, 0})\n}\n\nfunc DefaultIsTerminal() bool {\n\treturn true\n}\n\nfunc DefaultOnWidthChanged(func()) {\n\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/vim.go",
    "content": "package readline\n\nconst (\n\tVIM_NORMAL = iota\n\tVIM_INSERT\n\tVIM_VISUAL\n)\n\ntype opVim struct {\n\tcfg     *Config\n\top      *Operation\n\tvimMode int\n}\n\nfunc newVimMode(op *Operation) *opVim {\n\tov := &opVim{\n\t\tcfg: op.cfg,\n\t\top:  op,\n\t}\n\tov.SetVimMode(ov.cfg.VimMode)\n\treturn ov\n}\n\nfunc (o *opVim) SetVimMode(on bool) {\n\tif o.cfg.VimMode && !on { // turn off\n\t\to.ExitVimMode()\n\t}\n\to.cfg.VimMode = on\n\to.vimMode = VIM_INSERT\n}\n\nfunc (o *opVim) ExitVimMode() {\n\to.vimMode = VIM_INSERT\n}\n\nfunc (o *opVim) IsEnableVimMode() bool {\n\treturn o.cfg.VimMode\n}\n\nfunc (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) {\n\trb := o.op.buf\n\thandled = true\n\tswitch r {\n\tcase 'h':\n\t\tt = CharBackward\n\tcase 'j':\n\t\tt = CharNext\n\tcase 'k':\n\t\tt = CharPrev\n\tcase 'l':\n\t\tt = CharForward\n\tcase '0', '^':\n\t\trb.MoveToLineStart()\n\tcase '$':\n\t\trb.MoveToLineEnd()\n\tcase 'x':\n\t\trb.Delete()\n\t\tif rb.IsCursorInEnd() {\n\t\t\trb.MoveBackward()\n\t\t}\n\tcase 'r':\n\t\trb.Replace(readNext())\n\tcase 'd':\n\t\tnext := readNext()\n\t\tswitch next {\n\t\tcase 'd':\n\t\t\trb.Erase()\n\t\tcase 'w':\n\t\t\trb.DeleteWord()\n\t\tcase 'h':\n\t\t\trb.Backspace()\n\t\tcase 'l':\n\t\t\trb.Delete()\n\t\t}\n\tcase 'b', 'B':\n\t\trb.MoveToPrevWord()\n\tcase 'w', 'W':\n\t\trb.MoveToNextWord()\n\tcase 'e', 'E':\n\t\trb.MoveToEndWord()\n\tcase 'f', 'F', 't', 'T':\n\t\tnext := readNext()\n\t\tprevChar := r == 't' || r == 'T'\n\t\treverse := r == 'F' || r == 'T'\n\t\tswitch next {\n\t\tcase CharEsc:\n\t\tdefault:\n\t\t\trb.MoveTo(next, prevChar, reverse)\n\t\t}\n\tdefault:\n\t\treturn r, false\n\t}\n\treturn t, true\n}\n\nfunc (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) {\n\trb := o.op.buf\n\thandled = true\n\tswitch r {\n\tcase 'i':\n\tcase 'I':\n\t\trb.MoveToLineStart()\n\tcase 'a':\n\t\trb.MoveForward()\n\tcase 'A':\n\t\trb.MoveToLineEnd()\n\tcase 's':\n\t\trb.Delete()\n\tcase 'S':\n\t\trb.Erase()\n\tcase 'c':\n\t\tnext := readNext()\n\t\tswitch next {\n\t\tcase 'c':\n\t\t\trb.Erase()\n\t\tcase 'w':\n\t\t\trb.DeleteWord()\n\t\tcase 'h':\n\t\t\trb.Backspace()\n\t\tcase 'l':\n\t\t\trb.Delete()\n\t\t}\n\tdefault:\n\t\treturn r, false\n\t}\n\n\to.EnterVimInsertMode()\n\treturn\n}\n\nfunc (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) {\n\tswitch r {\n\tcase CharEnter, CharInterrupt:\n\t\to.ExitVimMode()\n\t\treturn r\n\t}\n\n\tif r, handled := o.handleVimNormalMovement(r, readNext); handled {\n\t\treturn r\n\t}\n\n\tif r, handled := o.handleVimNormalEnterInsert(r, readNext); handled {\n\t\treturn r\n\t}\n\n\t// invalid operation\n\to.op.t.Bell()\n\treturn 0\n}\n\nfunc (o *opVim) EnterVimInsertMode() {\n\to.vimMode = VIM_INSERT\n}\n\nfunc (o *opVim) ExitVimInsertMode() {\n\to.vimMode = VIM_NORMAL\n}\n\nfunc (o *opVim) HandleVim(r rune, readNext func() rune) rune {\n\tif o.vimMode == VIM_NORMAL {\n\t\treturn o.HandleVimNormal(r, readNext)\n\t}\n\tif r == CharEsc {\n\t\to.ExitVimInsertMode()\n\t\treturn 0\n\t}\n\n\tswitch o.vimMode {\n\tcase VIM_INSERT:\n\t\treturn r\n\tcase VIM_VISUAL:\n\t}\n\treturn r\n}\n"
  },
  {
    "path": "cmd/nash/vendor/github.com/chzyer/readline/windows_api.go",
    "content": "// +build windows\n\npackage readline\n\nimport (\n\t\"reflect\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nvar (\n\tkernel = NewKernel()\n\tstdout = uintptr(syscall.Stdout)\n\tstdin  = uintptr(syscall.Stdin)\n)\n\ntype Kernel struct {\n\tSetConsoleCursorPosition,\n\tSetConsoleTextAttribute,\n\tFillConsoleOutputCharacterW,\n\tFillConsoleOutputAttribute,\n\tReadConsoleInputW,\n\tGetConsoleScreenBufferInfo,\n\tGetConsoleCursorInfo,\n\tGetStdHandle CallFunc\n}\n\ntype short int16\ntype word uint16\ntype dword uint32\ntype wchar uint16\n\ntype _COORD struct {\n\tx short\n\ty short\n}\n\nfunc (c *_COORD) ptr() uintptr {\n\treturn uintptr(*(*int32)(unsafe.Pointer(c)))\n}\n\nconst (\n\tEVENT_KEY                = 0x0001\n\tEVENT_MOUSE              = 0x0002\n\tEVENT_WINDOW_BUFFER_SIZE = 0x0004\n\tEVENT_MENU               = 0x0008\n\tEVENT_FOCUS              = 0x0010\n)\n\ntype _KEY_EVENT_RECORD struct {\n\tbKeyDown          int32\n\twRepeatCount      word\n\twVirtualKeyCode   word\n\twVirtualScanCode  word\n\tunicodeChar       wchar\n\tdwControlKeyState dword\n}\n\n// KEY_EVENT_RECORD          KeyEvent;\n// MOUSE_EVENT_RECORD        MouseEvent;\n// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;\n// MENU_EVENT_RECORD         MenuEvent;\n// FOCUS_EVENT_RECORD        FocusEvent;\ntype _INPUT_RECORD struct {\n\tEventType word\n\tPadding   uint16\n\tEvent     [16]byte\n}\n\ntype _CONSOLE_SCREEN_BUFFER_INFO struct {\n\tdwSize              _COORD\n\tdwCursorPosition    _COORD\n\twAttributes         word\n\tsrWindow            _SMALL_RECT\n\tdwMaximumWindowSize _COORD\n}\n\ntype _SMALL_RECT struct {\n\tleft   short\n\ttop    short\n\tright  short\n\tbottom short\n}\n\ntype _CONSOLE_CURSOR_INFO struct {\n\tdwSize   dword\n\tbVisible bool\n}\n\ntype CallFunc func(u ...uintptr) error\n\nfunc NewKernel() *Kernel {\n\tk := &Kernel{}\n\tkernel32 := syscall.NewLazyDLL(\"kernel32.dll\")\n\tv := reflect.ValueOf(k).Elem()\n\tt := v.Type()\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tname := t.Field(i).Name\n\t\tf := kernel32.NewProc(name)\n\t\tv.Field(i).Set(reflect.ValueOf(k.Wrap(f)))\n\t}\n\treturn k\n}\n\nfunc (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc {\n\treturn func(args ...uintptr) error {\n\t\tvar r0 uintptr\n\t\tvar e1 syscall.Errno\n\t\tsize := uintptr(len(args))\n\t\tif len(args) <= 3 {\n\t\t\tbuf := make([]uintptr, 3)\n\t\t\tcopy(buf, args)\n\t\t\tr0, _, e1 = syscall.Syscall(p.Addr(), size,\n\t\t\t\tbuf[0], buf[1], buf[2])\n\t\t} else {\n\t\t\tbuf := make([]uintptr, 6)\n\t\t\tcopy(buf, args)\n\t\t\tr0, _, e1 = syscall.Syscall6(p.Addr(), size,\n\t\t\t\tbuf[0], buf[1], buf[2], buf[3], buf[4], buf[5],\n\t\t\t)\n\t\t}\n\n\t\tif int(r0) == 0 {\n\t\t\tif e1 != 0 {\n\t\t\t\treturn error(e1)\n\t\t\t} else {\n\t\t\t\treturn syscall.EINVAL\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n}\n\nfunc GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) {\n\tt := new(_CONSOLE_SCREEN_BUFFER_INFO)\n\terr := kernel.GetConsoleScreenBufferInfo(\n\t\tstdout,\n\t\tuintptr(unsafe.Pointer(t)),\n\t)\n\treturn t, err\n}\n\nfunc GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) {\n\tt := new(_CONSOLE_CURSOR_INFO)\n\terr := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t)))\n\treturn t, err\n}\n\nfunc SetConsoleCursorPosition(c *_COORD) error {\n\treturn kernel.SetConsoleCursorPosition(stdout, c.ptr())\n}\n"
  },
  {
    "path": "cmd/nash/vendor.sh",
    "content": "#!/usr/bin/env nash\n\nfn vendor() {\n        cwdir <= pwd | xargs echo -n\n        vendordir = $cwdir + \"/vendor\"\n        rm -rf $vendordir\n\n        bindir = $vendordir + \"/bin\"\n        srcdir = $vendordir + \"/src\"\n        pkgdir = $vendordir + \"/pkg\"\n        mkdir -p $bindir $srcdir $pkgdir\n\n        setenv GOPATH = $vendordir\n        setenv GOBIN = $vendordir\n\n        go get -v .\n\n        rawpaths <= ls $srcdir\n        paths <= split($paths, \"\\n\")\n        for path in $paths {\n                mv $srcdir + $path $vendor\n        }\n        rm -rf $bindir $srcdir $pkgdir\n\n        # because nash library is a dependency of cmd/nash\n        # we need to remove it at end\n        rm -rf vendor/github.com/madlambda\n}\n\nvendor()\n"
  },
  {
    "path": "cmd/nashfmt/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/madlambda/nash/parser\"\n)\n\nvar (\n\toverwrite bool\n\tversion   bool\n\t// version is set at build time\n\tVersionString = \"No version provided\"\n)\n\nfunc init() {\n\tflag.BoolVar(&overwrite, \"w\", false, \"overwrite file\")\n\tflag.BoolVar(&version, \"version\", false, \"Show version\")\n}\n\nfunc main() {\n\tvar (\n\t\tfile io.ReadCloser\n\t\terr  error\n\t)\n\n\tflag.Parse()\n\n\tif version {\n\t\tfmt.Printf(\"build tag: %s\\n\", VersionString)\n\t\treturn\n\t}\n\n\tif len(flag.Args()) <= 0 {\n\t\tflag.PrintDefaults()\n\t\treturn\n\t}\n\n\tfname := flag.Args()[0]\n\n\tfile, err = os.Open(fname)\n\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %s\\n\", err.Error())\n\t\tos.Exit(1)\n\t}\n\n\tcontent, err := ioutil.ReadAll(file)\n\n\tif err != nil {\n\t\tfile.Close()\n\t\tfmt.Fprintf(os.Stderr, \"error: %s\\n\", err.Error())\n\t\tos.Exit(1)\n\t}\n\n\tparser := parser.NewParser(\"nashfmt\", string(content))\n\n\tast, err := parser.Parse()\n\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", err.Error())\n\t\tfile.Close()\n\t\tos.Exit(1)\n\t}\n\n\tfile.Close()\n\n\tif !overwrite {\n\t\tfmt.Printf(\"%s\\n\", ast.String())\n\t\treturn\n\t}\n\n\tif ast.String() != string(content) {\n\t\terr = ioutil.WriteFile(fname, []byte(fmt.Sprintf(\"%s\\n\", ast.String())), 0666)\n\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"error: %s\\n\", err.Error())\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "docs/interactive.md",
    "content": "<!-- mdtocstart -->\n\n# Table of Contents\n\n- [Line mode](#line-mode)\n- [Autocomplete](#autocomplete)\n- [Hooks](#hooks)\n- [bindfn](#bindfn)\n\n<!-- mdtocend -->\n\nWhen used as an interactive shell, nash supports a few features to\nenhance user experience.\n\n# Line mode\n\nNash supports line editing with `emacs` and `vim` modes. The default\nmode is `emacs` but it can be changed by the command `set mode vim`,\nor setting the environment variable `LINEMODE` with desired value.\n\nWhen in emacs mode, the following shortcuts can be used:\n\n| Shortcut           | Comment                           |\n| ------------------ | --------------------------------- |\n| `Ctrl`+`A`         | Beginning of line                 |\n| `Ctrl`+`B` / `←`   | Backward one character            |\n| `Meta`+`B`         | Backward one word                 |\n| `Ctrl`+`C`         | Send io.EOF                       |\n| `Ctrl`+`D`         | Delete one character/Close nash   |\n| `Meta`+`D`         | Delete one word                   |\n| `Ctrl`+`E`         | End of line                       |\n| `Ctrl`+`F` / `→`   | Forward one character             |\n| `Meta`+`F`         | Forward one word                  |\n| `Ctrl`+`H`         | Delete previous character         |\n| `Ctrl`+`I` / `Tab` | Command line completion           |\n| `Ctrl`+`J`         | Line feed                         |\n| `Ctrl`+`K`         | Cut text to the end of line       |\n| `Ctrl`+`L`         | Clear screen                      |\n| `Ctrl`+`M`         | Same as Enter key                 |\n| `Ctrl`+`T`         | Transpose characters              |\n| `Ctrl`+`U`         | Cut text to the beginning of line |\n| `Ctrl`+`W`         | Cut previous word                 |\n| `Backspace`        | Delete previous character         |\n| `Meta`+`Backspace` | Cut previous word                 |\n| `Enter`            | Line feed                         |\n\n# Autocomplete\n\nNash doesn't have autocomplete built in, but it do has triggers to you\nimplement it yourself.\n\nEvery time the `TAB` or `CTRL-I (in emacs mode)` is pressed, nash\nlooks for a function called `nash_complete` declared in the\nenvironment and calls it passing the line buffer and cursor position.\n\nThe function must make the autocomplete using some external software\n(like [fzf fuzzy finder](https://github.com/junegunn/fzf)) and then\nreturn the characters to be completed. Below is a simple example to\nautocomplete system binaries using `fzf`:\n\n```sh\nfn diffword(complete, line) {\n    diff <= echo -n $complete | sed \"s#^\"+$line+\"##g\" | tr -d \"\\n\"\n\n    return $diff\n}\n\nfn nash_complete(line, pos) {\n    ret = ()\n    parts <= split($line, \"\\n\")\n\n    choice <= (\n\t\tfind /bin /usr/bin -maxdepth 1 -type f |\n\t\tsed \"s#/.*/##g\" |\n\t\tsort -u |\n\t\t-fzf -q \"^\"+$line\n\t\t\t\t-1\n\t\t\t\t-0\n\t\t\t\t--header \"Looking for system-wide binaries\"\n\t\t\t\t--prompt \"(λ programs)>\"\n\t\t\t\t--reverse\n\n\t)\n\n    if $status != \"0\" {\n        return $ret\n    }\n\n    choice <= diffword($choice, $line)\n\n\tret = ($choice+\" \" \"0\")\n\n\treturn $ret\n}\n```\n\n# Hooks\n\nThere are two functions that can be used to update the environment\nwhile typing commands. The function `nash_repl_before` is called every\ntime in the cli main loop *before* the printing of the `PROMPT`\nvariable (and before user can type any command). And the function\ncalled `nash_repl_after` is called every time in the cli main loop\ntoo, but *after* the command was interpreted and executed.\n\nSee the examples below:\n\n```sh\nDEFPROMPT = \"λ> \"\nfn nash_repl_before() {\n    # do something before prompt is ready\n    datetime <= date \"+%d/%m/%y %H:%M:%S\"\n    PROMPT = \"(\"+$datetime+\")\"+$DEFPROMPT\n    setenv PROMPT\n}\n\nfn nash_repl_after(line, status) {\n    # do something after command was executed\n    # line and status are the command issued and their\n    # exit status (if applicable)\n}\n```\n\n# bindfn\n\nFunctions are commonly used for nash libraries,\nbut when needed it can be bind'ed to some command name,\nso it can be used as a command from your shell prompt.\n\nFor example, lets implement a **cd** using a function and bindfn.\nFirst define the function:\n\n```nash\nfn cd(path) {\n    fullpath <= realpath $path | xargs echo -n\n    chdir($path)\n    PROMPT=\"[\" + $fullpath + \"]> \"\n    setenv PROMPT\n}\n```\n\nUsing the **cd** function above, we can override the built-in\n**cd** with that function with the **bindfn** statement.\n\n```nash\nλ> bindfn cd cd\nλ> cd /var/log\n[/var/log]>\n```\n\nThe bindfn syntax is:\n\n```nash\nbindfn <function-name> <cmd-name>\n```\n"
  },
  {
    "path": "docs/reference.md",
    "content": "<!-- mdtocstart -->\n\n# Table of Contents\n\n- [Command line arguments](#command-line-arguments)\n- [Flow control](#flow-control)\n    - [Branching](#branching)\n    - [Looping](#looping)\n        - [Lists](#lists)\n        - [Forever](#forever)\n- [Functions](#functions)\n- [Operators](#operators)\n    - [+](#)\n        - [string](#string)\n- [Packages](#packages)\n- [Iterating](#iterating)\n- [Built-in functions](#builtin-functions)\n    - [print](#print)\n    - [format](#format)\n    - [len](#len)\n    - [append](#append)\n    - [exit](#exit)\n    - [glob](#glob)\n- [Standard Library](#standard-library)\n\n<!-- mdtocend -->\n\nHere lies a comprehensive reference documentation of nash\nfeatures and built-in functions, and how to use them.\n\nThere is also some [examples](./examples) that can be useful.\n\n\n# Command line arguments\n\nTo handle script arguments you can just use the ARGS variable,\nthat is a list populated with the arguments passed to your script\nwhen it is executed, like:\n\n```nash\necho\necho \"acessing individual parameter\"\nvar somearg = $ARGS[0]\necho $somearg\necho\n```\n\n# Flow control\n\n## Branching\n\nTo branch you can use **if** statement, it requires\na boolean expression, like the comparison operator:\n\n```nash\nvar a = \"nash\"\necho -n $a\nif $a == \"nash\" {\n    a = \"rocks\"\n}\necho $a\n#Output:\"nashrocks\"\n```\n\nYou can also use a junction of boolean expressions:\n\n```nash\na = \"nash\"\nb = \"rocks\"\nif $a == \"nash\" && $b == \"rocks\"{\n    echo \"hellyeah\"\n}\n#Output:\"hellyeah\"\n```\n\nYou can also use a disjunction of boolean expressions:\n\n```nash\na = \"nash\"\nb = \"rocks\"\nif $a == \"bash\" || $b == \"rocks\"{\n    echo \"hellyeah\"\n}\n#Output:\"hellyeah\"\n```\n\n## Looping\n\nRight now there are two kind of loops, on lists\nand the forever kind :-).\n\n### Lists\n\nYou can iterate lists like this:\n\n```nash\na = \"\"\nfor i in (\"nash\" \"rocks\"){\n    a = $a + $i\n}\necho $a\n#Output:\"nashrocks\"\n```\n\n### Forever\n\nIt would be cool to loop on boolean expressions, but\nright now we can only loop forever (besides list\nlooping):\n\n```nash\nfor {\n    echo \"hi\"\n}\n```\n\n# Functions\n\nDefining functions is very easy, for example:\n\n```nash\nfn concat(a, b) {\n        return $a+$b\n}\n\nres <= concat(\"1\",\"9\")\necho $res\n\n#Output:\"19\"\n```\n\nIf a parameter is missing on the function call,\nit will fail:\n\n```nash\nfn concat(a, b) {\n        return $a, $b\n}\n\nres <= concat(\"1\")\necho $res\n\n#Output:\"ERROR: Wrong number of arguments for function concat. Expected 2 but found 1\"\n```\n\nPassing extra parameters will also fail:\n\n```nash\nfn concat(a, b) {\n        return $a, $b\n}\n\nres <= concat(\"1\",\"2\",\"3\")\necho $res\n\n#Output:\"ERROR: Wrong number of arguments for function concat. Expected 2 but found 3\"\n```\n\n# Operators\n\n## +\n\nThe **+** operator behaviour\nis dependent on the type its operands. It\nis always invalid to mix types on the operation\n(like one operand is a string and the other one is a integer).\n\nThe language is dynamically typed, but it is strongly\ntyped, types can't be mixed on operations, there is no\nimplicit type coercion.\n\n### string\n\nString concatenation is pretty straightforward.\nFor example:\n\n```nash\na = \"1\"\nb = \"2\"\n\necho $a+$b\n#Output:\"12\"\n```\n\n# Packages\n\nTODO\n\n# Iterating\n\nTODO\n\n# Built-in functions\n\nBuilt-in functions are functions that are embedded on the\nlanguage. You do not have to import any package to use them.\n\n## print\n\nThe function **print** is used to print simple\nmessages directly to stdout:\n\n```nash\nprint(\"hi\")\n#Output:\"hi\"\n```\n\nAnd supports formatting:\n\n```nash\nprint(\"%s:%s\", \"1\", \"2\")\n#Output:\"1:2\"\n```\n\n## format\n\nThe function **format** is used like **print**, but\ninstead of writing to stdout it will return the string\naccording to the format provided:\n\n```nash\na <= format(\"%s:%s\", \"1\", \"2\")\necho $a\n#Output:\"1:2\"\n```\n\n## len\n\nThe function **len** returns the length of a list.\nAn example to check for the length of a list:\n\n```\necho \"define one list with two elements\"\nargs = (\n    \"one\"\n    \"two\"\n)\necho \"getting list length\"\nargslen <= len($args)\necho $argslen\n```\n\n## append\n\nThe function **append** appends one element to the end of a exist list.\nAppend returns the updated list.\n\nAn example to append one element to a exist list:\n\n```\nexample_list = ()\necho \"appending string 1\"\nexample_list <= append($example_list, \"1\")\necho $example_list\necho \"appending string 2\"\nexample_list <= append($example_list, \"2\")\necho $example_list\n```\n\n## exit\n\nTODO\n\n## glob\n\nTODO\n\n# Standard Library\n\nThe standard library is a set of packages that comes with the\nnash install (although not obligatory).\n\nThey must be imported explicitly (as any other package) to\nbe used.\n\n* [fmt](docs/stdlib/fmt.md)\n"
  },
  {
    "path": "docs/stdlib/fmt.md",
    "content": "<!-- mdtocstart -->\n\n# Table of Contents\n\n- [fmt](#fmt)\n    - [fmt_println](#fmtprintln)\n\n<!-- mdtocend -->\n\n# fmt\n\n## fmt_println\n\nSame behavior as print but adds a newline on the end.\n\n```nash\nimport fmt\n\nfmt_println(\"hi\")\n#Output:\"hi\\n\"\n```\n"
  },
  {
    "path": "errors/error.go",
    "content": "package errors\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/scanner\"\n)\n\ntype (\n\tNashError struct {\n\t\treason string\n\t\tformat string\n\t}\n\n\tunfinished struct{}\n\n\tunfinishedBlockError struct {\n\t\t*NashError\n\t\tunfinished\n\t}\n\n\tunfinishedListError struct {\n\t\t*NashError\n\t\tunfinished\n\t}\n\n\tunfinishedCmdError struct {\n\t\t*NashError\n\t\tunfinished\n\t}\n)\n\nfunc NewError(format string, arg ...interface{}) *NashError {\n\te := &NashError{}\n\te.SetReason(format, arg...)\n\treturn e\n}\n\nfunc NewEvalError(path string, node ast.Node, format string, arg ...interface{}) *NashError {\n\tlinenum := fmt.Sprintf(\"%s:%d:%d: \", path, node.Line(), node.Column())\n\treturn NewError(linenum+format, arg...)\n}\n\nfunc (e *NashError) SetReason(format string, arg ...interface{}) {\n\te.reason = fmt.Sprintf(format, arg...)\n}\n\nfunc (e *NashError) Error() string { return e.reason }\n\nfunc (e unfinished) Unfinished() bool { return true }\n\nfunc NewUnfinishedBlockError(name string, it scanner.Token) error {\n\treturn &unfinishedBlockError{\n\t\tNashError: NewError(\"%s:%d:%d: Statement's block '{' not finished\",\n\t\t\tname, it.Line(), it.Column()),\n\t}\n}\n\nfunc NewUnfinishedListError(name string, it scanner.Token) error {\n\treturn &unfinishedListError{\n\t\tNashError: NewError(\"%s:%d:%d: List assignment not finished. Found %v\",\n\t\t\tname, it.Line(), it.Column(), it),\n\t}\n}\n\nfunc NewUnfinishedCmdError(name string, it scanner.Token) error {\n\treturn &unfinishedCmdError{\n\t\tNashError: NewError(\"%s:%d:%d: Multi-line command not finished. Found %v but expect ')'\",\n\t\t\tname, it.Line(), it.Column(), it),\n\t}\n}\n"
  },
  {
    "path": "examples/append.sh",
    "content": "#!/usr/bin/env nash\n\nvar example_list = ()\necho \"appending string 1\"\nexample_list <= append($example_list, \"1\")\necho $example_list\necho \"appending string 2\"\nexample_list <= append($example_list, \"2\")\necho $example_list\n"
  },
  {
    "path": "examples/args.sh",
    "content": "#!/usr/bin/env nash\n\nprint(\"iterating through the arguments list\\n\\n\")\nfor arg in $ARGS {\n    print(\"%s\\n\", $arg)\n}\n\nprint(\"\\n\")\nprint(\"acessing individual parameter\\n\")\nvar somearg = $ARGS[0]\nprint(\"%s\\n\", $somearg)\n"
  },
  {
    "path": "examples/init",
    "content": "#!/usr/bin/env nash\n\n# Simple init script for you base your own\n# For a more complete and organized .nash see:\n# https://github.com/tiago4orion/dotnash\n\n# PROMPT is a special variable used by nash command line to\n# setup your prompt.\nvar RED = \"\u001b[31m\"\nvar GREEN = \"\u001b[32m\"\nvar RESET = \"\u001b[0m\"\nvar PROMPTSYM = \"λ> \"\nvar DEFPROMPT = $RED+$PROMPTSYM+$RESET\n\nsetenv PROMPT = $DEFPROMPT\n\n# cd overrides built-in cd\n# Add the current branch before prompt (if current directory is a git repo)\nfn cd(path) {\n\tvar branch = \"\"\n\tvar prompt = \"\"\n\n\tif $path == \"\" {\n\t\tpath = $HOME\n\t}\n\n\tchdir($path)\n\n\tvar _, status <= test -d ./.git\n\n\tif $status != \"0\" {\n\t\tprompt = $DEFPROMPT\n\t} else {\n\t\tbranch <= git rev-parse --abbrev-ref HEAD | xargs echo -n\n\n\t\tprompt = \"(\"+$GREEN+$branch+$RESET+\")\"+$DEFPROMPT\n\t}\n\n\tsetenv PROMPT = $prompt\n}\n\nbindfn cd cd\n\n# syntax sugar to cd into go project\nfn gocd(path) {\n\tcd $GOPATH+\"/src/\"+$path\n}\n\nbindfn gocd gocd\n"
  },
  {
    "path": "examples/len.sh",
    "content": "#!/usr/bin/env nash\n\necho \"args: \"\necho $ARGS\n\nif len($ARGS) == \"1\" {\n        echo \"one parameter passed\"\n} else {\n        echo \"more parameters passed\"\n}\n"
  },
  {
    "path": "examples_test.go",
    "content": "package nash_test\n\nimport (\n\t\"os\"\n\t\"io/ioutil\"\n\t\n\t\"github.com/madlambda/nash\"\n)\n\nfunc Example() {\n\n\tnashpath,cleanup := tmpdir()\n\tdefer cleanup()\n\t\n\tnashroot, cleanup := tmpdir()\n\tdefer cleanup()\n\n\tnash, err := nash.New(nashpath, nashroot)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Execute a script from string\n\terr = nash.ExecuteString(\"-input-\", `echo Hello World`)\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Output: Hello World\n}\n\nfunc tmpdir() (string, func()) {\t\n\tdir, err := ioutil.TempDir(\"\", \"nash-tests\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t\n\treturn dir, func() {\n\t\terr := os.RemoveAll(dir)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "fuzz.go",
    "content": "// +build gofuzz\n\npackage nash\n\nimport \"github.com/madlambda/nash/parser\"\n\nfunc Fuzz(data []byte) int {\n\tp := parser.NewParser(\"fuzz\", string(data))\n\n\ttree, err := p.Parse()\n\n\tif err != nil {\n\t\tif tree != nil {\n\t\t\tpanic(\"tree != nil\")\n\t\t}\n\n\t\treturn 0\n\t}\n\n\treturn 1\n}\n"
  },
  {
    "path": "hack/check.sh",
    "content": "#!/bin/bash\n\nset -e\n\ngo test -race -coverprofile=coverage.txt ./...\n\necho \"running stdlib and stdbin tests\"\ntests=$(find ./stdlib ./stdbin -name \"*_test.sh\")\n\nfor t in ${tests[*]}\ndo\n    echo\n    echo \"running test: \"$t\n    ./cmd/nash/nash $t\n    echo \"success\"\ndone\n"
  },
  {
    "path": "hack/install/unix.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nwhich wget >/dev/null   || { echo \"wget not installed\"; exit 1; } \nwhich tar >/dev/null    || { echo \"tar not installed\"; exit 1; }\nwhich tr >/dev/null     || { echo \"tr not found\"; exit 1; }\n\nNASHROOT=${NASHROOT:-$HOME/nashroot}\nVERSION=\"v1.1\"\nARCH=\"amd64\"\nOS=\"$(uname | tr '[:upper:]' '[:lower:]')\"\n\nif [ $# -eq 1 ]; then\n        VERSION=$1\nfi\n\necho \"installing nash (${OS}): ${VERSION} at NASHROOT: ${NASHROOT}\"\n\nmkdir -p $NASHROOT\ncd $NASHROOT\ntarfile=\"nash-${VERSION}-${OS}-${ARCH}.tar.gz\"\nwget -c https://github.com/madlambda/nash/releases/download/$VERSION/$tarfile\ntar xvfz $tarfile\nrm -f $tarfile\n"
  },
  {
    "path": "hack/releaser.sh",
    "content": "#!/usr/bin/env nash\n\nif len($ARGS) != \"2\" {\n\tprint(\"usage: %s <version>\\n\\n\", $ARGS[0])\n\texit(\"1\")\n}\n\nvar version = $ARGS[1]\nvar supported_os = (\"linux\" \"darwin\" \"windows\")\nvar supported_arch = (\"amd64\")\n\n# Guarantee passing tests at least on the host arch/os\nmake test\nmkdir -p dist\n\nfn prepare_execs(distfiles, os) {\n\tif $os == \"windows\" {\n\t\tvar newfiles = ()\n\t\t\n\t\tfor distfile in $distfiles {\n\t\t\tvar src = $distfile[0]\n\t\t\tvar dst = $distfile[1]\n\t\t\tvar newsrc = $src+\".exe\"\n\t\t\tvar newdst = $dst+\".exe\"\n\t\t\n\t\t\tnewfiles <= append($newfiles, ($newsrc $newdst))\n\t\t}\n\t\t\n\t\treturn $newfiles\n\t}\n\tif $os == \"linux\" {\n\t\tfor distfile in $distfiles {\n\t\t\tstrip $distfile[0]\n\t\t}\n\t}\n\n\treturn $distfiles\n}\n\nfor os in $supported_os {\n\tfor arch in $supported_arch {\n\t\tsetenv GOOS = $os\n\t\tsetenv GOARCH = $arch\n\n\t\tif $os == \"linux\" {\n\t\t\tsetenv CGO_ENABLED = \"1\"\n\t\t} else {\n\t\t\tsetenv CGO_ENABLED = \"0\"\n\t\t}\n\n\t\techo \"building OS: \"+$GOOS+\" ARCH : \"+$GOARCH\n\t\tmake build \"version=\"+$version\n\n\t\tvar pkgdir <= mktemp -d\n\n\t\tvar bindir = $pkgdir+\"/bin\"\n\t\tvar stdlibdir = $pkgdir+\"/stdlib\"\n\n\t\tmkdir -p $bindir\n\t\tmkdir -p $stdlibdir\n\n\t\tvar nash_src = \"./cmd/nash/nash\"\n\t\tvar nash_dst = $bindir+\"/nash\"\n\t\tvar nashfmt_src = \"./cmd/nashfmt/nashfmt\"\n\t\tvar nashfmt_dst = $bindir+\"/nashfmt\"\n\t\tvar execfiles = (\n\t\t\t($nash_src $nash_dst)\n\t\t\t($nashfmt_src $nashfmt_dst)\n\t\t)\n\n\t\tvar execfiles <= prepare_execs($execfiles, $os)\n\n\t\t# TODO: Improve with glob, right now have only two packages =)\n\t\tvar distfiles <= append($execfiles, (\"./stdlib/io.sh\" $stdlibdir))\n\n\t\tdistfiles <= append($distfiles, (\"./stdlib/map.sh\" $stdlibdir))\n\n\t\tfor distfile in $distfiles {\n\t\t\tvar src = $distfile[0]\n\t\t\tvar dst = $distfile[1]\n\n\t\t\tcp -pr $src $dst\n\t\t}\n\n\t\tvar projectdir <= pwd\n\t\tvar distar <= format(\"%s/dist/nash-%s-%s-%s.tar.gz\", $projectdir, $version, $os, $arch)\n\n\t\tchdir($pkgdir)\n\n\t\tvar pkgraw <= ls\n\t\tvar pkgfiles <= split($pkgraw, \"\\n\")\n\n\t\ttar cvfz $distar $pkgfiles\n\n\t\tchdir($projectdir)\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/append.go",
    "content": "package builtin\n\nimport (\n\t\"io\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tappendFn struct {\n\t\tobj  []sh.Obj\n\t\targs []sh.Obj\n\t}\n)\n\nfunc newAppend() *appendFn {\n\treturn &appendFn{}\n}\n\nfunc (appendfn *appendFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"list\", false),\n\t\tsh.NewFnArg(\"value...\", true), // variadic\n\t}\n}\n\nfunc (appendfn *appendFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\tnewobj := append(appendfn.obj, appendfn.args...)\n\treturn []sh.Obj{sh.NewListObj(newobj)}, nil\n}\n\nfunc (appendfn *appendFn) SetArgs(args []sh.Obj) error {\n\tif len(args) < 2 {\n\t\treturn errors.NewError(\"append expects at least two arguments\")\n\t}\n\n\tobj := args[0]\n\tif obj.Type() != sh.ListType {\n\t\treturn errors.NewError(\"append expects a list as first argument, but a %s was provided\",\n\t\t\tobj.Type())\n\t}\n\n\tvalues := args[1:]\n\tif objlist, ok := obj.(*sh.ListObj); ok {\n\t\tappendfn.obj = objlist.List()\n\t\tappendfn.args = values\n\t\treturn nil\n\t}\n\n\treturn errors.NewError(\"internal error: object of wrong type\")\n}\n"
  },
  {
    "path": "internal/sh/builtin/append_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n)\n\ntype testcase struct {\n\tname           string\n\tcode           string\n\texpectedErr    string\n\texpectedStdout string\n\texpectedStderr string\n}\n\nfunc testAppend(t *testing.T, tc testcase) {\n\tsh, teardown := fixture.SetupShell(t)\n\tdefer teardown()\n\n\tvar (\n\t\toutb, errb bytes.Buffer\n\t)\n\tsh.SetStdout(&outb)\n\tsh.SetStderr(&errb)\n\n\terr := sh.Exec(tc.name, tc.code)\n\tstdout := string(outb.Bytes())\n\tstderr := errb.String()\n\n\tif stdout != tc.expectedStdout {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", tc.expectedStdout, stdout)\n\t\treturn\n\t}\n\tif stderr != tc.expectedStderr {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", tc.expectedStderr, stderr)\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\tif err.Error() != tc.expectedErr {\n\t\t\tt.Fatalf(\"Expected err '%s' but got '%s'\", tc.expectedErr, err.Error())\n\t\t}\n\t} else if tc.expectedErr != \"\" {\n\t\tt.Fatalf(\"Expected err '%s' but err is nil\", tc.expectedErr)\n\t}\n}\n\nfunc TestAppend(t *testing.T) {\n\tfor _, tc := range []testcase{\n\t\t{\n\t\t\tname:        \"no argument fails\",\n\t\t\tcode:        `append()`,\n\t\t\texpectedErr: \"<interactive>:1:0: append expects at least two arguments\",\n\t\t},\n\t\t{\n\t\t\tname:        \"one argument fails\",\n\t\t\tcode:        `append(\"1\")`,\n\t\t\texpectedErr: \"<interactive>:1:0: append expects at least two arguments\",\n\t\t},\n\t\t{\n\t\t\tname: \"simple append\",\n\t\t\tcode: `var a = ()\n\t\t a <= append($a, \"hello\")\n\t\t a <= append($a, \"world\")\n\t\t echo -n $a...`,\n\t\t\texpectedErr:    \"\",\n\t\t\texpectedStdout: \"hello world\",\n\t\t\texpectedStderr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"append is for lists\",\n\t\t\tcode: `var a = \"something\"\n\t\t a <= append($a, \"other\")\n\t\t echo -n $a...`,\n\t\t\texpectedErr: \"<interactive>:2:8: append expects a \" +\n\t\t\t\t\"list as first argument, but a StringType was provided\",\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"var args\",\n\t\t\tcode: `var a <= append((), \"1\", \"2\", \"3\", \"4\", \"5\", \"6\")\n\t\t\t\techo -n $a...`,\n\t\t\texpectedErr:    \"\",\n\t\t\texpectedStdout: \"1 2 3 4 5 6\",\n\t\t\texpectedStderr: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"append of lists\",\n\t\t\tcode: `var a <= append((), (), ())\n\t\t\t\tif len($a) != \"2\" {\n\t\t\t\t\tprint(\"wrong\")\n\t\t\t\t} else if len($a[0]) != \"0\" {\n\t\t\t\t\tprint(\"wrong\")\n\t\t\t\t} else if len($a[1]) != \"0\" {\n\t\t\t\t\tprint(\"wrong\")\n\t\t\t\t} else { print(\"ok\") }`,\n\t\t\texpectedErr:    \"\",\n\t\t\texpectedStdout: \"ok\",\n\t\t},\n\t} {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttestAppend(t, tc)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "internal/sh/builtin/chdir.go",
    "content": "package builtin\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tchdirFn struct {\n\t\targ string\n\t}\n)\n\nfunc newChdir() *chdirFn {\n\treturn &chdirFn{}\n}\n\nfunc (chdir *chdirFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"dir\", false),\n\t}\n}\n\nfunc (chdir *chdirFn) Run(in io.Reader, out io.Writer, ioerr io.Writer) ([]sh.Obj, error) {\n\terr := os.Chdir(chdir.arg)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"builtin: chdir: error[%s] path[%s]\", err, chdir.arg)\n\t}\n\treturn nil, err\n}\n\nfunc (chdir *chdirFn) SetArgs(args []sh.Obj) error {\n\tif len(args) != 1 {\n\t\treturn errors.NewError(\"chdir expects one argument, but received %q\", args)\n\t}\n\n\tobj := args[0]\n\tif obj.Type() != sh.StringType {\n\t\treturn errors.NewError(\"chdir expects a string, but a %s was provided\", obj.Type())\n\t}\n\n\tif objstr, ok := obj.(*sh.StrObj); ok {\n\t\tchdir.arg = objstr.Str()\n\t\treturn nil\n\t}\n\n\treturn errors.NewError(\"internal error: object of wrong type\")\n}\n"
  },
  {
    "path": "internal/sh/builtin/doc.go",
    "content": "// builtin is where all built in functions resides\npackage builtin\n"
  },
  {
    "path": "internal/sh/builtin/exec_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"testing\"\n\t\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n)\n\nfunc execSuccess(t *testing.T, scriptContents string) string {\n\tshell, cleanup := fixture.SetupShell(t)\n\tdefer cleanup()\n\n\tout, err := shell.ExecOutput(\"\", scriptContents)\n\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected err: %s\", err)\n\t}\n\treturn string(out)\n}\n\nfunc execFailure(t *testing.T, scriptContents string) {\n\tshell, cleanup := fixture.SetupShell(t)\n\tdefer cleanup()\n\t\n\tout, err := shell.ExecOutput(\"\", scriptContents)\n\n\tif err == nil {\n\t\tt.Fatalf(\"expected err, got success, output: %s\", string(out))\n\t}\n\n\tif len(out) > 0 {\n\t\tt.Fatalf(\"expected empty output, got: %s\", string(out))\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/exit.go",
    "content": "package builtin\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\texitFn struct {\n\t\tstatus int\n\t}\n)\n\nfunc newExit() Fn {\n\treturn &exitFn{}\n}\n\nfunc (e *exitFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"status\", false),\n\t}\n}\n\nfunc (e *exitFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\tos.Exit(e.status)\n\treturn nil, nil //Unrecheable code\n}\n\nfunc (e *exitFn) SetArgs(args []sh.Obj) error {\n\tif len(args) != 1 {\n\t\treturn errors.NewError(\"exit expects 1 argument\")\n\t}\n\n\tobj := args[0]\n\tif obj.Type() != sh.StringType {\n\t\treturn errors.NewError(\n\t\t\t\"exit expects a status string, but a %s was provided\",\n\t\t\tobj.Type(),\n\t\t)\n\t}\n\tstatusstr := obj.(*sh.StrObj).Str()\n\tstatus, err := strconv.Atoi(statusstr)\n\tif err != nil {\n\t\treturn errors.NewError(\n\t\t\t\"exit:error[%s] converting status[%s] to int\",\n\t\t\terr,\n\t\t\tstatusstr,\n\t\t)\n\n\t}\n\te.status = status\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/builtin/exit_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n)\n\nfunc TestExit(t *testing.T) {\n\ttype exitDesc struct {\n\t\tscript string\n\t\tstatus string\n\t\tresult int\n\t\tfail   bool\n\t}\n\n\ttests := map[string]exitDesc{\n\t\t\"success\": {\n\t\t\tscript: \"./testdata/exit.sh\",\n\t\t\tstatus: \"0\",\n\t\t\tresult: 0,\n\t\t},\n\t\t\"failure\": {\n\t\t\tscript: \"./testdata/exit.sh\",\n\t\t\tstatus: \"1\",\n\t\t\tresult: 1,\n\t\t},\n\t}\n\n\t// WHY: We need to run Exec because the script will call the exit syscall,\n\t// killing the process (the test process on this case).\n\t// When calling Exec we need to guarantee that we are using the nash\n\t// built directly from the project, not the one installed on the host.\n\tprojectnash := \"../../../cmd/nash/nash\"\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tcmd := exec.Command(projectnash, desc.script, desc.status)\n\t\t\tcmd.Stdout = os.Stdout\n\t\t\tcmd.Stderr = os.Stderr // to know why scripts were failing\n\t\t\tcmd.Run()\n\n\t\t\tif cmd.ProcessState == nil {\n\t\t\t\tt.Fatalf(\"expected cmd[%v] to have a process state, can't validate status code\", cmd)\n\t\t\t}\n\t\t\tgot := cmd.ProcessState.ExitCode()\n\t\t\tif desc.result != got {\n\t\t\t\tt.Fatalf(\"expected[%d] got[%d]\", desc.result, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/format.go",
    "content": "package builtin\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tformatFn struct {\n\t\tfmt  string\n\t\targs []interface{}\n\t}\n)\n\nfunc newFormat() *formatFn {\n\treturn &formatFn{}\n}\n\nfunc (f *formatFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"fmt\", false),\n\t\tsh.NewFnArg(\"arg...\", true),\n\t}\n}\n\nfunc (f *formatFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\treturn []sh.Obj{sh.NewStrObj(fmt.Sprintf(f.fmt, f.args...))}, nil\n}\n\nfunc (f *formatFn) SetArgs(args []sh.Obj) error {\n\tif len(args) == 0 {\n\t\treturn errors.NewError(\"format expects at least 1 argument\")\n\t}\n\n\tf.fmt = args[0].String()\n\tf.args = nil\n\n\tfor _, arg := range args[1:] {\n\t\tf.args = append(f.args, arg.String())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/builtin/format_test.go",
    "content": "package builtin_test\n\nimport \"testing\"\n\nfunc TestFormat(t *testing.T) {\n\ttype formatDesc struct {\n\t\tscript string\n\t\toutput string\n\t}\n\n\ttests := map[string]formatDesc{\n\t\t\"textonly\": {\n\t\t\tscript: `\n\t\t\t\tvar r <= format(\"helloworld\")\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"helloworld\\n\",\n\t\t},\n\t\t\"ncallsRegressionTest\": {\n\t\t\tscript: `\n\t\t\t\tfn formatstuff() {\n\t\t\t\t\tvar r <= format(\"hello%s\", \"world\")\n\t\t\t\t\techo $r\n\t\t\t\t}\n\t\t\t\tformatstuff()\n\t\t\t\tformatstuff()\n\t\t\t`,\n\t\t\toutput: \"helloworld\\nhelloworld\\n\",\n\t\t},\n\t\t\"ncallsWithVarsRegressionTest\": {\n\t\t\tscript: `\n\t\t\t\tfn formatstuff() {\n\t\t\t\t\tvar b = \"world\"\n\t\t\t\t\tvar r <= format(\"hello%s\", $b)\n\t\t\t\t\tvar s <= format(\"hackthe%s\", $b)\n\t\t\t\t\techo $r\n\t\t\t\t\techo $s\n\t\t\t\t}\n\t\t\t\tformatstuff()\n\t\t\t\tformatstuff()\n\t\t\t`,\n\t\t\toutput: \"helloworld\\nhacktheworld\\nhelloworld\\nhacktheworld\\n\",\n\t\t},\n\t\t\"fmtstring\": {\n\t\t\tscript: `\n\t\t\t\tvar r <= format(\"%s:%s\", \"hello\", \"world\")\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"hello:world\\n\",\n\t\t},\n\t\t\"fmtlist\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"1\" \"2\" \"3\")\n\t\t\t\tvar r <= format(\"%s:%s\", \"list\", $list)\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"list:1 2 3\\n\",\n\t\t},\n\t\t\"funconly\": {\n\t\t\tscript: `\n\t\t\t\tfn func() {}\n\t\t\t\tvar r <= format($func)\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"<fn func>\\n\",\n\t\t},\n\t\t\"funcfmt\": {\n\t\t\tscript: `\n\t\t\t\tfn func() {}\n\t\t\t\tvar r <= format(\"calling:%s\", $func)\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"calling:<fn func>\\n\",\n\t\t},\n\t\t\"listonly\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"1\" \"2\" \"3\")\n\t\t\t\tvar r <= format($list)\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"1 2 3\\n\",\n\t\t},\n\t\t\"listoflists\": {\n\t\t\tscript: `\n\t\t\t\tvar list = ((\"1\" \"2\" \"3\") (\"4\" \"5\" \"6\"))\n\t\t\t\tvar r <= format(\"%s:%s\", \"listoflists\", $list)\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"listoflists:1 2 3 4 5 6\\n\",\n\t\t},\n\t\t\"listasfmt\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"%s\" \"%s\")\n\t\t\t\tvar r <= format($list, \"1\", \"2\")\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"1 2\\n\",\n\t\t},\n\t\t\"invalidFmt\": {\n\t\t\tscript: `\n\t\t\t\tvar r <= format(\"%d%s\", \"invalid\")\n\t\t\t\techo $r\n\t\t\t`,\n\t\t\toutput: \"%!d(string=invalid)%!s(MISSING)\\n\",\n\t\t},\n\t}\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\toutput := execSuccess(t, desc.script)\n\t\t\tif output != desc.output {\n\t\t\t\tt.Fatalf(\"got %q expected %q\", output, desc.output)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFormatfErrors(t *testing.T) {\n\ttype formatDesc struct {\n\t\tscript string\n\t}\n\n\ttests := map[string]formatDesc{\n\t\t\"noParams\": {script: `format()`},\n\t}\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\texecFailure(t, desc.script)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/glob.go",
    "content": "package builtin\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tglobFn struct {\n\t\tpattern string\n\t}\n)\n\nfunc newGlob() *globFn {\n\treturn &globFn{}\n}\n\nfunc (p *globFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{sh.NewFnArg(\"pattern\", false)}\n}\n\nfunc (g *globFn) Run(in io.Reader, out io.Writer, e io.Writer) ([]sh.Obj, error) {\n\tlistobjs := []sh.Obj{}\n\tmatches, err := filepath.Glob(g.pattern)\n\tif err != nil {\n\t\treturn []sh.Obj{\n\t\t\tsh.NewListObj([]sh.Obj{}),\n\t\t\tsh.NewStrObj(fmt.Sprintf(\"glob:error: %q\", err)),\n\t\t}, nil\n\t}\n\tfor _, match := range matches {\n\t\tlistobjs = append(listobjs, sh.NewStrObj(match))\n\t}\n\treturn []sh.Obj{sh.NewListObj(listobjs), sh.NewStrObj(\"\")}, nil\n}\n\nfunc (g *globFn) SetArgs(args []sh.Obj) error {\n\tif len(args) != 1 {\n\t\treturn errors.NewError(\"glob expects 1 string argument (the pattern)\")\n\t}\n\n\tobj := args[0]\n\tif obj.Type() != sh.StringType {\n\t\treturn errors.NewError(\n\t\t\t\"glob expects a pattern string, but a %s was provided\",\n\t\t\tobj.Type(),\n\t\t)\n\t}\n\tg.pattern = obj.(*sh.StrObj).Str()\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/builtin/glob_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc setup(t *testing.T) (string, func()) {\n\tdir, err := ioutil.TempDir(\"\", \"globtest\")\n\tif err != nil {\n\t\tt.Fatalf(\"error on setup: %s\", err)\n\t}\n\n\treturn dir, func() {\n\t\tos.RemoveAll(dir)\n\t}\n}\n\nfunc createFile(t *testing.T, path string) {\n\tf, err := os.Create(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tf.WriteString(\"hi\")\n\tf.Close()\n}\n\nfunc TestGlobNoResult(t *testing.T) {\n\tdir, teardown := setup(t)\n\tdefer teardown()\n\n\tpattern := dir + \"/*.la\"\n\n\tout := execSuccess(t, fmt.Sprintf(`\n\t\tvar res, err <= glob(\"%s\")\n\t\tprint($res)\n\t\tprint($err)\n\t\tprint(len($res))\n\t`, pattern))\n\n\tif out != \"0\" {\n\t\tt.Fatalf(\"expected no results, got: %q\", out)\n\t}\n}\n\nfunc TestGlobOneResult(t *testing.T) {\n\tdir, teardown := setup(t)\n\tdefer teardown()\n\n\tfilename := dir + \"/whatever.go\"\n\tcreateFile(t, filename)\n\tpattern := dir + \"/*.go\"\n\n\tout := execSuccess(t, fmt.Sprintf(`\n\t\tvar res, err <= glob(\"%s\")\n\t\tprint($res)\n\t\tprint($err)\n\t`, pattern))\n\n\tif out != filename {\n\t\tt.Fatalf(\"expected %q, got: %q\", filename, out)\n\t}\n}\n\nfunc TestGlobMultipleResults(t *testing.T) {\n\tdir, teardown := setup(t)\n\tdefer teardown()\n\n\tfilename1 := dir + \"/whatever.h\"\n\tfilename2 := dir + \"/whatever2.h\"\n\n\tcreateFile(t, filename1)\n\tcreateFile(t, filename2)\n\n\tpattern := dir + \"/*.h\"\n\n\tout := execSuccess(t, fmt.Sprintf(`\n\t\tvar res, err <= glob(\"%s\")\n\t\tprint($res)\n\t\tprint($err)\n\t`, pattern))\n\n\tres := strings.Split(out, \" \")\n\tif len(res) != 2 {\n\t\tt.Fatalf(\"expected 2 results, got: %d\", len(res))\n\t}\n\n\tfound1 := false\n\tfound2 := false\n\n\tfor _, r := range res {\n\t\tif r == filename1 {\n\t\t\tfound1 = true\n\t\t}\n\t\tif r == filename2 {\n\t\t\tfound2 = true\n\t\t}\n\t}\n\n\tif !found1 || !found2 {\n\t\tt.Fatalf(\"unable to found all files, got: %q\", out)\n\t}\n}\n\nfunc TestGlobInvalidPatternError(t *testing.T) {\n\tout := execSuccess(t, `\n\t\tvar res, err <= glob(\"*[.go\")\n\t\tprint($res)\n\t\tif $err != \"\" {\n\t\t\tprint(\"got error\")\n\t\t}\n\t`)\n\n\tif out != \"got error\" {\n\t\tt.Fatalf(\"expected error message on glob, got nothing, output[%s]\", out)\n\t}\n}\n\nfunc TestFatalFailure(t *testing.T) {\n\ttype tcase struct {\n\t\tname string\n\t\tcode string\n\t}\n\tcases := []tcase{\n\t\ttcase{\n\t\t\tname: \"noParam\",\n\t\t\tcode: `\n\t\t\t\tvar res <= glob()\n\t\t\t\tprint($res)\n\t\t\t`,\n\t\t},\n\t\ttcase{\n\t\t\tname: \"multipleParams\",\n\t\t\tcode: `\n\t\t\t\tvar res <= glob(\"/a/*\", \"/b/*\")\n\t\t\t\tprint($res)\n\t\t\t`,\n\t\t},\n\t\ttcase{\n\t\t\tname: \"wrongType\",\n\t\t\tcode: `\n\t\t\t\tvar param = (\"hi\")\n\t\t\t\tvar res <= glob($param)\n\t\t\t\tprint($res)\n\t\t\t`,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\texecFailure(t, c.code)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/len.go",
    "content": "package builtin\n\nimport (\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tlenFn struct {\n\t\targ sh.Collection\n\t}\n)\n\nfunc newLen() *lenFn {\n\treturn &lenFn{}\n}\n\nfunc (l *lenFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"list\", false),\n\t}\n}\n\nfunc lenresult(res int) []sh.Obj {\n\treturn []sh.Obj{sh.NewStrObj(strconv.Itoa(res))}\n}\n\nfunc (l *lenFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\treturn lenresult(l.arg.Len()), nil\n}\n\nfunc (l *lenFn) SetArgs(args []sh.Obj) error {\n\tif len(args) != 1 {\n\t\treturn errors.NewError(\"lenfn expects one argument\")\n\t}\n\n\tobj := args[0]\n\tcol, err := sh.NewCollection(obj)\n\tif err != nil {\n\t\treturn errors.NewError(\"len:error[%s]\", err)\n\t}\n\n\tl.arg = col\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/builtin/len_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n)\n\nfunc TestLen(t *testing.T) {\n\tsh, cleanup := fixture.SetupShell(t)\n\tdefer cleanup()\n\n\tvar out bytes.Buffer\n\n\tsh.SetStdout(&out)\n\n\terr := sh.Exec(\n\t\t\"test len\",\n\t\t`var a = (1 2 3 4 5 6 7 8 9 0)\n\t\t var len_a <= len($a)\n\t\t echo -n $len_a`,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif \"10\" != string(out.Bytes()) {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", \"10\", string(out.Bytes()))\n\t\treturn\n\t}\n\n\t// Having to reset is a bad example of why testing N stuff together\n\t// is asking for trouble :-).\n\tout.Reset()\n\n\terr = sh.Exec(\n\t\t\"test len fail\",\n\t\t`a = \"test\"\n\t\t var l <= len($a)\n\t\t echo -n $l\n\t\t`,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif \"4\" != string(out.Bytes()) {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", \"4\", string(out.Bytes()))\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/loader.go",
    "content": "package builtin\n\nimport (\n\t\"io\"\n\n\t\"github.com/madlambda/nash/sh\"\n)\n\n// Fn is the contract of a built in function, that is simpler\n// than the core nash Fn.\ntype (\n\tFn interface {\n\t\tArgNames() []sh.FnArg\n\t\tSetArgs(args []sh.Obj) error\n\t\tRun(\n\t\t\tstdin io.Reader,\n\t\t\tstdout io.Writer,\n\t\t\tstderr io.Writer,\n\t\t) ([]sh.Obj, error)\n\t}\n\n\tConstructor func() Fn\n)\n\n// Constructors returns a map of the builtin function name and its constructor\nfunc Constructors() map[string]Constructor {\n\treturn map[string]Constructor{\n\t\t\"glob\":   func() Fn { return newGlob() },\n\t\t\"print\":  func() Fn { return newPrint() },\n\t\t\"format\": func() Fn { return newFormat() },\n\t\t\"split\":  func() Fn { return newSplit() },\n\t\t\"len\":    func() Fn { return newLen() },\n\t\t\"chdir\":  func() Fn { return newChdir() },\n\t\t\"append\": func() Fn { return newAppend() },\n\t\t\"exit\":   func() Fn { return newExit() },\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/print.go",
    "content": "package builtin\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tprintFn struct {\n\t\tfmt  string\n\t\targs []interface{}\n\t}\n)\n\nfunc newPrint() *printFn {\n\treturn &printFn{}\n}\n\nfunc (p *printFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"fmt\", false),\n\t\tsh.NewFnArg(\"arg...\", true),\n\t}\n}\n\nfunc (p *printFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\tfmt.Fprintf(out, p.fmt, p.args...)\n\treturn nil, nil\n}\n\nfunc (p *printFn) SetArgs(args []sh.Obj) error {\n\tif len(args) == 0 {\n\t\treturn errors.NewError(\"print expects at least 1 argument\")\n\t}\n\n\tp.fmt = args[0].String()\n\tp.args = nil\n\tfor _, arg := range args[1:] {\n\t\tp.args = append(p.args, arg.String())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/builtin/print_test.go",
    "content": "package builtin_test\n\nimport \"testing\"\n\nfunc TestPrint(t *testing.T) {\n\ttype printDesc struct {\n\t\tscript string\n\t\toutput string\n\t}\n\n\ttests := map[string]printDesc{\n\t\t\"textonly\": {\n\t\t\tscript: `print(\"helloworld\")`,\n\t\t\toutput: \"helloworld\",\n\t\t},\n\t\t\"nCallsRegresion\": {\n\t\t\tscript: `print(\"%s:%s\", \"hello\", \"world\")`,\n\t\t\toutput: \"hello:world\",\n\t\t},\n\t\t\"fmtstring\": {\n\t\t\tscript: `\n\t\t\t\tprint(\"%s:%s\", \"hello\", \"world\")\n\t\t\t\tprint(\"%s:%s\", \"hello\", \"world\")\n\t\t\t`,\n\t\t\toutput: \"hello:worldhello:world\",\n\t\t},\n\t\t\"fmtlist\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"1\" \"2\" \"3\")\n\t\t\t\tprint(\"%s:%s\", \"list\", $list)\n\t\t\t`,\n\t\t\toutput: \"list:1 2 3\",\n\t\t},\n\t\t\"funconly\": {\n\t\t\tscript: `\n\t\t\t\tfn func() {}\n\t\t\t\tprint($func)\n\t\t\t`,\n\t\t\toutput: \"<fn func>\",\n\t\t},\n\t\t\"funcfmt\": {\n\t\t\tscript: `\n\t\t\t\tfn func() {}\n\t\t\t\tprint(\"calling:%s\", $func)\n\t\t\t`,\n\t\t\toutput: \"calling:<fn func>\",\n\t\t},\n\t\t\"listonly\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"1\" \"2\" \"3\")\n\t\t\t\tprint($list)\n\t\t\t`,\n\t\t\toutput: \"1 2 3\",\n\t\t},\n\t\t\"listoflists\": {\n\t\t\tscript: `\n\t\t\t\tvar list = ((\"1\" \"2\" \"3\") (\"4\" \"5\" \"6\"))\n\t\t\t\tprint(\"%s:%s\", \"listoflists\", $list)\n\t\t\t`,\n\t\t\toutput: \"listoflists:1 2 3 4 5 6\",\n\t\t},\n\t\t\"listasfmt\": {\n\t\t\tscript: `\n\t\t\t\tvar list = (\"%s\" \"%s\")\n\t\t\t\tprint($list, \"1\", \"2\")\n\t\t\t`,\n\t\t\toutput: \"1 2\",\n\t\t},\n\t\t\"invalidFmt\": {\n\t\t\tscript: `print(\"%d%s\", \"invalid\")`,\n\t\t\toutput: \"%!d(string=invalid)%!s(MISSING)\",\n\t\t},\n\t}\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\toutput := execSuccess(t, desc.script)\n\t\t\tif output != desc.output {\n\t\t\t\tt.Fatalf(\"got %q expected %q\", output, desc.output)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPrintErrors(t *testing.T) {\n\ttype printDesc struct {\n\t\tscript string\n\t}\n\n\ttests := map[string]printDesc{\n\t\t\"noParams\": {\n\t\t\tscript: `print()`,\n\t\t},\n\t}\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\texecFailure(t, desc.script)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/builtin/split.go",
    "content": "package builtin\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tsplitFn struct {\n\t\tcontent string\n\t\tsep     sh.Obj\n\t}\n)\n\nfunc newSplit() *splitFn {\n\treturn &splitFn{}\n}\n\nfunc (s *splitFn) ArgNames() []sh.FnArg {\n\treturn []sh.FnArg{\n\t\tsh.NewFnArg(\"sep\", false),\n\t\tsh.NewFnArg(\"content\", false),\n\t}\n}\n\nfunc (s *splitFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {\n\tvar output []string\n\n\tcontent := s.content\n\n\tswitch s.sep.Type() {\n\tcase sh.StringType:\n\t\tsep := s.sep.(*sh.StrObj).Str()\n\t\toutput = strings.Split(content, sep)\n\tcase sh.ListType:\n\t\tsepList := s.sep.(*sh.ListObj).List()\n\t\toutput = splitByList(content, sepList)\n\tcase sh.FnType:\n\t\tsepFn := s.sep.(*sh.FnObj).Fn()\n\t\toutput = splitByFn(content, sepFn)\n\tdefault:\n\t\treturn nil, errors.NewError(\"Invalid separator value: %v\", s.sep)\n\t}\n\n\tlistobjs := make([]sh.Obj, len(output))\n\tfor i := 0; i < len(output); i++ {\n\t\tlistobjs[i] = sh.NewStrObj(output[i])\n\t}\n\n\treturn []sh.Obj{sh.NewListObj(listobjs)}, nil\n}\n\nfunc (s *splitFn) SetArgs(args []sh.Obj) error {\n\tif len(args) != 2 {\n\t\treturn errors.NewError(\"split: expects 2 parameters\")\n\t}\n\n\tif args[0].Type() != sh.StringType {\n\t\treturn errors.NewError(\"split: first parameter must be a string\")\n\t}\n\n\tcontent := args[0].(*sh.StrObj)\n\ts.content = content.Str()\n\ts.sep = args[1]\n\treturn nil\n}\n\nfunc splitByList(content string, delims []sh.Obj) []string {\n\treturn strings.FieldsFunc(content, func(r rune) bool {\n\t\tfor _, delim := range delims {\n\t\t\tif delim.Type() != sh.StringType {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tobjstr := delim.(*sh.StrObj)\n\n\t\t\tif len(objstr.Str()) > 0 && rune(objstr.Str()[0]) == r {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t})\n}\n\nfunc splitByFn(content string, splitFunc sh.FnDef) []string {\n\treturn strings.FieldsFunc(content, func(r rune) bool {\n\t\tfn := splitFunc.Build()\n\t\targ := sh.NewStrObj(string(r))\n\t\tfn.SetArgs([]sh.Obj{arg})\n\t\terr := fn.Start()\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\terr = fn.Wait()\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tresults := fn.Results()\n\t\tif len(results) != 1 {\n\t\t\t// expects a single return fn\n\t\t\treturn false\n\t\t}\n\n\t\tresult := results[0]\n\n\t\t//FIXME: It would be cool to only accept booleans\n\t\t// since the splitter is a predicate\n\t\tif result.Type() != sh.StringType {\n\t\t\treturn false\n\t\t}\n\n\t\tstatus := result.(*sh.StrObj)\n\t\tif status.Str() == \"0\" {\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t})\n}\n"
  },
  {
    "path": "internal/sh/builtin/split_test.go",
    "content": "package builtin_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n)\n\nfunc TestSplit(t *testing.T) {\n\ttype splitDesc struct {\n\t\tscript string\n\t\tword   string\n\t\tsep    string\n\t\tresult string\n\t}\n\n\ttests := map[string]splitDesc{\n\t\t\"space\": {\n\t\t\tscript: \"./testdata/split.sh\",\n\t\t\tword:   \"a b c\",\n\t\t\tsep:    \" \",\n\t\t\tresult: \"a\\nb\\nc\\n\",\n\t\t},\n\t\t\"pipes\": {\n\t\t\tscript: \"./testdata/split.sh\",\n\t\t\tword:   \"1|2|3\",\n\t\t\tsep:    \"|\",\n\t\t\tresult: \"1\\n2\\n3\\n\",\n\t\t},\n\t\t\"nosplit\": {\n\t\t\tscript: \"./testdata/split.sh\",\n\t\t\tword:   \"nospaces\",\n\t\t\tsep:    \" \",\n\t\t\tresult: \"nospaces\\n\",\n\t\t},\n\t\t\"splitfunc\": {\n\t\t\tscript: \"./testdata/splitfunc.sh\",\n\t\t\tword:   \"hah\",\n\t\t\tsep:    \"a\",\n\t\t\tresult: \"h\\nh\\n\",\n\t\t},\n\t}\n\n\tfor name, desc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar output bytes.Buffer\n\t\t\tshell, cleanup := fixture.SetupShell(t)\n\t\t\tdefer cleanup()\n\t\t\t\n\t\t\tshell.SetStdout(&output)\n\t\t\terr := shell.ExecFile(desc.script, \"mock cmd name\", desc.word, desc.sep)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected err: %s\", err)\n\t\t\t}\n\n\t\t\tif output.String() != desc.result {\n\t\t\t\tt.Fatalf(\"got %q expected %q\", output.String(), desc.result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSplitingByFuncWrongWontPanic(t *testing.T) {\n\t// Regression for: https://github.com/madlambda/nash/issues/177\n\n\tbadscripts := map[string]string{\n\t\t\"noReturnValue\": `\n\t\t\tfn b() { echo \"oops\" }\n\t\t\tsplit(\"lalala\", $b)\n\t\t`,\n\t\t\"returnValueIsList\": `\n\t\t\tfn b() { return () }\n\t\t\tsplit(\"lalala\", $b)\n\t\t`,\n\t\t\"returnValueIsFunc\": `\n\t\t\tfn b() { \n\t\t\t\tfn x () {}\n\t\t\t\treturn $x\n\t\t\t}\n\t\t\tsplit(\"lalala\", $b)\n\t\t`,\n\t}\n\n\tfor testname, badscript := range badscripts {\n\n\t\tt.Run(testname, func(t *testing.T) {\n\t\t\tshell, cleanup := fixture.SetupShell(t)\n\t\t\tdefer cleanup()\n\t\t\t\n\t\t\t_, err := shell.ExecOutput(\"whatever\", badscript)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n\n"
  },
  {
    "path": "internal/sh/builtin/testdata/exit.sh",
    "content": "#!/usr/bin/env nash\n\nexit($ARGS[1])\n"
  },
  {
    "path": "internal/sh/builtin/testdata/split.sh",
    "content": "#!/usr/bin/env nash\n\nvar word = $ARGS[1]\nvar sep = $ARGS[2]\nvar output <= split($word, $sep)\nfor o in $output {\n\techo $o\n}\n"
  },
  {
    "path": "internal/sh/builtin/testdata/splitfunc.sh",
    "content": "#!/usr/bin/env nash\n\nvar word = $ARGS[1]\nvar sep = $ARGS[2]\n\nfn splitter(char) {\n\tif $char == $sep {\n\t\treturn \"0\"\n\t}\n\n\treturn \"1\"\n}\n\nvar output <= split($word, $splitter)\n\nfor o in $output {\n\techo $o\n}\n"
  },
  {
    "path": "internal/sh/builtin.go",
    "content": "package sh\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/internal/sh/builtin\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\t// builtinFn maps a builtin function to a nash sh.FnDef\n\t// avoiding a lot of duplicated code and decoupling the\n\t// builtin functions of some unnecessary details on how\n\t// the sh.Fn works (lots of complexity to provide features of\n\t// other kinds of runners/functions).\n\tbuiltinFn struct {\n\t\tstdin          io.Reader\n\t\tstdout, stderr io.Writer\n\n\t\tdone    chan struct{}\n\t\terr     error\n\t\tresults []sh.Obj\n\n\t\tname string\n\t\tfn   builtin.Fn\n\t}\n)\n\nfunc NewBuiltinFn(\n\tname string,\n\tfn builtin.Fn,\n\tin io.Reader,\n\tout io.Writer,\n\touterr io.Writer,\n) *builtinFn {\n\treturn &builtinFn{\n\t\tname:   name,\n\t\tfn:     fn,\n\t\tstdin:  in,\n\t\tstdout: out,\n\t\tstderr: outerr,\n\t}\n}\n\nfunc (f *builtinFn) Name() string {\n\treturn f.name\n}\n\nfunc (f *builtinFn) ArgNames() []sh.FnArg {\n\treturn f.fn.ArgNames()\n}\n\nfunc (f *builtinFn) Start() error {\n\tf.done = make(chan struct{})\n\n\tgo func() {\n\t\tf.results, f.err = f.fn.Run(f.stdin, f.stdout, f.stderr)\n\t\tf.done <- struct{}{}\n\t}()\n\n\treturn nil\n}\n\nfunc (f *builtinFn) Wait() error {\n\t<-f.done\n\treturn f.err\n}\n\nfunc (f *builtinFn) Results() []sh.Obj {\n\treturn f.results\n}\n\nfunc (f *builtinFn) String() string {\n\treturn fmt.Sprintf(\"<builtin function %q>\", f.Name())\n}\n\nfunc (f *builtinFn) SetArgs(args []sh.Obj) error {\n\treturn f.fn.SetArgs(args)\n}\n\nfunc (f *builtinFn) SetEnviron(env []string) {\n\t// do nothing\n\t// terrible design smell having functions that do nothing =/\n}\n\nfunc (f *builtinFn) SetStdin(r io.Reader) {\n\tf.stdin = r\n}\nfunc (f *builtinFn) SetStderr(w io.Writer) {\n\tf.stderr = w\n}\nfunc (f *builtinFn) SetStdout(w io.Writer) {\n\tf.stdout = w\n}\nfunc (f *builtinFn) StdoutPipe() (io.ReadCloser, error) {\n\treturn nil, errors.NewError(\"builtin functions doesn't works with pipes\")\n}\nfunc (f *builtinFn) Stdin() io.Reader  { return f.stdin }\nfunc (f *builtinFn) Stdout() io.Writer { return f.stdout }\nfunc (f *builtinFn) Stderr() io.Writer { return f.stderr }\n"
  },
  {
    "path": "internal/sh/cmd.go",
    "content": "package sh\n\nimport (\n\t\"io\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\t// Cmd is a nash command. It has maps of input and output file\n\t// descriptors that can be set by SetInputfd and SetOutputfd.\n\t// This can be used to pipe execution of Cmd commands.\n\tCmd struct {\n\t\t*exec.Cmd\n\n\t\targExprs []ast.Expr\n\t}\n\n\t// errCmdNotFound is an error indicating the command wasn't found.\n\terrCmdNotFound struct {\n\t\t*errors.NashError\n\t}\n)\n\nfunc newCmdNotFound(format string, arg ...interface{}) error {\n\te := &errCmdNotFound{\n\t\tNashError: errors.NewError(format, arg...),\n\t}\n\n\treturn e\n}\n\nfunc (e *errCmdNotFound) NotFound() bool {\n\treturn true\n}\n\nfunc NewCmd(name string) (*Cmd, error) {\n\tvar (\n\t\terr     error\n\t\tcmdPath = name\n\t)\n\n\tcmd := Cmd{}\n\n\tif !filepath.IsAbs(name) {\n\t\tcmdPath, err = exec.LookPath(name)\n\n\t\tif err != nil {\n\t\t\treturn nil, newCmdNotFound(err.Error())\n\t\t}\n\t}\n\n\tcmd.Cmd = &exec.Cmd{\n\t\tPath: cmdPath,\n\t}\n\n\treturn &cmd, nil\n}\n\nfunc (c *Cmd) Stdin() io.Reader  { return c.Cmd.Stdin }\nfunc (c *Cmd) Stdout() io.Writer { return c.Cmd.Stdout }\nfunc (c *Cmd) Stderr() io.Writer { return c.Cmd.Stderr }\n\nfunc (c *Cmd) SetStdin(in io.Reader)   { c.Cmd.Stdin = in }\nfunc (c *Cmd) SetStdout(out io.Writer) { c.Cmd.Stdout = out }\nfunc (c *Cmd) SetStderr(err io.Writer) { c.Cmd.Stderr = err }\n\nfunc (c *Cmd) SetArgs(nodeArgs []sh.Obj) error {\n\targs := make([]string, 1, len(nodeArgs)+1)\n\targs[0] = c.Path\n\n\tfor _, obj := range nodeArgs {\n\t\tif obj.Type() == sh.StringType {\n\t\t\tobjstr := obj.(*sh.StrObj)\n\t\t\targs = append(args, objstr.Str())\n\t\t} else if obj.Type() == sh.ListType {\n\t\t\tobjlist := obj.(*sh.ListObj)\n\t\t\tvalues := objlist.List()\n\n\t\t\tfor _, l := range values {\n\t\t\t\tif l.Type() != sh.StringType {\n\t\t\t\t\treturn errors.NewError(\"Command arguments requires string or list of strings. But received '%v'\", l.String())\n\t\t\t\t}\n\n\t\t\t\tlstr := l.(*sh.StrObj)\n\t\t\t\targs = append(args, lstr.Str())\n\t\t\t}\n\t\t} else if obj.Type() == sh.FnType {\n\t\t\treturn errors.NewError(\"Function cannot be passed as argument to commands.\")\n\t\t} else {\n\t\t\treturn errors.NewError(\"Invalid command argument '%v'\", obj)\n\t\t}\n\t}\n\n\tc.Cmd.Args = args\n\treturn nil\n}\n\nfunc (c *Cmd) Args() []ast.Expr { return c.argExprs }\n\nfunc (c *Cmd) SetEnviron(env []string) {\n\tc.Cmd.Env = env\n}\n\nfunc (c *Cmd) Wait() error {\n\terr := c.Cmd.Wait()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Cmd) Start() error {\n\terr := c.Cmd.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *Cmd) Results() []sh.Obj { return nil }\n"
  },
  {
    "path": "internal/sh/cmd_test.go",
    "content": "package sh\n"
  },
  {
    "path": "internal/sh/fncall.go",
    "content": "package sh\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tFnArg struct {\n\t\tName       string\n\t\tIsVariadic bool\n\t}\n\n\tUserFn struct {\n\t\targNames []sh.FnArg // argNames store parameter name\n\t\tdone     chan error // for async execution\n\t\tresults  []sh.Obj\n\n\t\tname     string // debugging purposes\n\t\tparent   *Shell\n\t\tsubshell *Shell\n\n\t\tenviron []string\n\n\t\tstdin          io.Reader\n\t\tstdout, stderr io.Writer\n\n\t\tbody           *ast.Tree\n\t\trepr           string\n\t\tcloseAfterWait []io.Closer\n\t}\n)\n\nfunc NewUserFn(name string, args []sh.FnArg, body *ast.Tree, parent *Shell) *UserFn {\n\tfn := &UserFn{\n\t\tname:     name,\n\t\targNames: args,\n\t\tbody:     body,\n\t\tdone:     make(chan error),\n\t\tparent:   parent,\n\t\tstdin:    parent.Stdin(),\n\t\tstdout:   parent.Stdout(),\n\t\tstderr:   parent.Stderr(),\n\t\tsubshell: NewSubShell(name, parent),\n\t}\n\n\tfn.subshell.SetTree(fn.body)\n\tfn.subshell.SetRepr(fn.repr)\n\tfn.subshell.SetDebug(fn.parent.debug)\n\tfn.subshell.SetStdout(fn.stdout)\n\tfn.subshell.SetStderr(fn.stderr)\n\tfn.subshell.SetStdin(fn.stdin)\n\tfn.subshell.SetEnviron(fn.environ)\n\treturn fn\n}\n\nfunc (fn *UserFn) ArgNames() []sh.FnArg { return fn.argNames }\n\nfunc (fn *UserFn) AddArgName(arg sh.FnArg) {\n\tfn.argNames = append(fn.argNames, arg)\n}\n\nfunc (fn *UserFn) SetArgs(args []sh.Obj) error {\n\tvar (\n\t\tisVariadic      bool\n\t\tcountNormalArgs int\n\t)\n\n\tfor i, argName := range fn.argNames {\n\t\tif argName.IsVariadic {\n\t\t\tif i != len(fn.argNames)-1 {\n\t\t\t\treturn errors.NewError(\"variadic expansion must be last argument\")\n\t\t\t}\n\t\t\tisVariadic = true\n\t\t} else {\n\t\t\tcountNormalArgs++\n\t\t}\n\t}\n\n\tif !isVariadic && len(args) != len(fn.argNames) {\n\t\treturn errors.NewError(\"Wrong number of arguments for function %s. \"+\n\t\t\t\"Expected %d but found %d\",\n\t\t\tfn.name, len(fn.argNames), len(args))\n\t}\n\n\tif isVariadic {\n\t\tif len(args) < countNormalArgs {\n\t\t\treturn errors.NewError(\"Wrong number of arguments for function %s. \"+\n\t\t\t\t\"Expected at least %d arguments but found %d\", fn.name,\n\t\t\t\tcountNormalArgs, len(args))\n\t\t}\n\n\t\tif len(args) == 0 {\n\t\t\t// there's only a variadic (optional) argument\n\t\t\t// and user supplied no argument...\n\t\t\t// then only initialize the variadic variable to\n\t\t\t// empty list\n\t\t\tfn.subshell.Newvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{}))\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tvar i int\n\tfor i = 0; i < len(fn.argNames) && i < len(args); i++ {\n\t\targ := args[i]\n\t\targName := fn.argNames[i].Name\n\t\tisVariadic := fn.argNames[i].IsVariadic\n\n\t\tif isVariadic {\n\t\t\tvar valist []sh.Obj\n\t\t\tfor ; i < len(args); i++ {\n\t\t\t\targ = args[i]\n\t\t\t\tvalist = append(valist, arg)\n\t\t\t}\n\t\t\tvalistarg := sh.NewListObj(valist)\n\t\t\tfn.subshell.Newvar(argName, valistarg)\n\t\t} else {\n\t\t\tfn.subshell.Newvar(argName, arg)\n\t\t}\n\t}\n\n\t// set remaining (variadic) list\n\tif len(fn.argNames) > 0 && i < len(fn.argNames) {\n\t\tlast := fn.argNames[len(fn.argNames)-1]\n\t\tif !last.IsVariadic {\n\t\t\treturn errors.NewError(\"internal error: optional arguments only for variadic parameter\")\n\t\t}\n\n\t\tfn.subshell.Newvar(last.Name, sh.NewListObj([]sh.Obj{}))\n\t}\n\n\treturn nil\n}\n\nfunc (fn *UserFn) Name() string { return fn.name }\n\nfunc (fn *UserFn) SetRepr(repr string) {\n\tfn.repr = repr\n}\n\nfunc (fn *UserFn) closeDescriptors(closers []io.Closer) {\n\tfor _, fd := range closers {\n\t\tfd.Close()\n\t}\n}\n\nfunc (fn *UserFn) execute() ([]sh.Obj, error) {\n\tif fn.body != nil {\n\t\treturn fn.subshell.ExecuteTree(fn.body)\n\t}\n\n\treturn nil, fmt.Errorf(\"fn not properly created\")\n}\n\nfunc (fn *UserFn) Start() error {\n\tgo func() {\n\t\tvar err error\n\t\tfn.results, err = fn.execute()\n\t\tfn.done <- err\n\t}()\n\n\treturn nil\n}\n\nfunc (fn *UserFn) Results() []sh.Obj { return fn.results }\n\nfunc (fn *UserFn) Wait() error {\n\terr := <-fn.done\n\n\tfn.closeDescriptors(fn.closeAfterWait)\n\tfn.subshell = nil\n\treturn err\n}\n\nfunc (fn *UserFn) SetEnviron(env []string) {\n\tfn.environ = env\n}\n\nfunc (fn *UserFn) SetStderr(w io.Writer) {\n\tfn.stderr = w\n}\n\nfunc (fn *UserFn) SetStdout(w io.Writer) {\n\tfn.stdout = w\n}\n\nfunc (fn *UserFn) SetStdin(r io.Reader) {\n\tfn.stdin = r\n}\n\nfunc (fn *UserFn) Stdin() io.Reader  { return fn.stdin }\nfunc (fn *UserFn) Stdout() io.Writer { return fn.stdout }\nfunc (fn *UserFn) Stderr() io.Writer { return fn.stderr }\n\nfunc (fn *UserFn) String() string {\n\tif fn.body != nil {\n\t\treturn fn.body.String()\n\t}\n\tpanic(\"fn not initialized\")\n}\n\nfunc (fn *UserFn) StdoutPipe() (io.ReadCloser, error) {\n\tpr, pw, err := os.Pipe()\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfn.subshell.SetStdout(pw)\n\n\t// As fn doesn't fork, both fd can be closed after wait is called\n\tfn.closeAfterWait = append(fn.closeAfterWait, pw, pr)\n\treturn pr, nil\n}\n"
  },
  {
    "path": "internal/sh/fndef.go",
    "content": "package sh\n\nimport (\n\t\"io\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/internal/sh/builtin\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\tfnDef struct {\n\t\tname     string\n\t\tParent   *Shell\n\t\tBody     *ast.Tree\n\t\targNames []sh.FnArg\n\n\t\tstdin          io.Reader\n\t\tstdout, stderr io.Writer\n\t\tenviron        []string\n\t}\n\n\tuserFnDef struct {\n\t\t*fnDef\n\t}\n\n\tbuiltinFnDef struct {\n\t\t*fnDef\n\t\tconstructor builtin.Constructor\n\t}\n)\n\n// newFnDef creates a new function definition\nfunc newFnDef(name string, parent *Shell, args []*ast.FnArgNode, body *ast.Tree) (*fnDef, error) {\n\tfn := fnDef{\n\t\tname:   name,\n\t\tParent: parent,\n\t\tBody:   body,\n\t\tstdin:  parent.stdin,\n\t\tstdout: parent.stdout,\n\t\tstderr: parent.stderr,\n\t}\n\n\tfor i := 0; i < len(args); i++ {\n\t\targ := args[i]\n\n\t\tif i < len(args)-1 && arg.IsVariadic {\n\t\t\treturn nil, errors.NewEvalError(parent.filename,\n\t\t\t\targ, \"Vararg '%s' isn't the last argument\",\n\t\t\t\targ.String())\n\t\t}\n\n\t\tfn.argNames = append(fn.argNames, sh.FnArg{arg.Name, arg.IsVariadic})\n\t}\n\treturn &fn, nil\n}\n\nfunc (fnDef *fnDef) Name() string         { return fnDef.name }\nfunc (fnDef *fnDef) ArgNames() []sh.FnArg { return fnDef.argNames }\nfunc (fnDef *fnDef) Environ() []string    { return fnDef.environ }\n\nfunc (fnDef *fnDef) SetEnviron(env []string) {\n\tfnDef.environ = env\n}\n\nfunc (fnDef *fnDef) SetStdin(r io.Reader) {\n\tfnDef.stdin = r\n}\n\nfunc (fnDef *fnDef) SetStderr(w io.Writer) {\n\tfnDef.stderr = w\n}\n\nfunc (fnDef *fnDef) SetStdout(w io.Writer) {\n\tfnDef.stdout = w\n}\n\nfunc (fnDef *fnDef) Stdin() io.Reader  { return fnDef.stdin }\nfunc (fnDef *fnDef) Stdout() io.Writer { return fnDef.stdout }\nfunc (fnDef *fnDef) Stderr() io.Writer { return fnDef.stderr }\n\nfunc newUserFnDef(name string, parent *Shell, args []*ast.FnArgNode, body *ast.Tree) (*userFnDef, error) {\n\tfnDef, err := newFnDef(name, parent, args, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tufndef := userFnDef{\n\t\tfnDef: fnDef,\n\t}\n\treturn &ufndef, nil\n}\n\nfunc (ufnDef *userFnDef) Build() sh.Fn {\n\tuserfn := NewUserFn(ufnDef.Name(), ufnDef.ArgNames(), ufnDef.Body, ufnDef.Parent)\n\tuserfn.SetStdin(ufnDef.stdin)\n\tuserfn.SetStdout(ufnDef.stdout)\n\tuserfn.SetStderr(ufnDef.stderr)\n\tuserfn.SetEnviron(ufnDef.environ)\n\treturn userfn\n}\n\nfunc newBuiltinFnDef(name string, parent *Shell, constructor builtin.Constructor) *builtinFnDef {\n\treturn &builtinFnDef{\n\t\tfnDef: &fnDef{\n\t\t\tname:   name,\n\t\t\tstdin:  parent.stdin,\n\t\t\tstdout: parent.stdout,\n\t\t\tstderr: parent.stderr,\n\t\t},\n\t\tconstructor: constructor,\n\t}\n}\n\nfunc (bfnDef *builtinFnDef) Build() sh.Fn {\n\treturn NewBuiltinFn(bfnDef.Name(),\n\t\tbfnDef.constructor(),\n\t\tbfnDef.stdin,\n\t\tbfnDef.stdout,\n\t\tbfnDef.stderr,\n\t)\n}\n"
  },
  {
    "path": "internal/sh/functions_test.go",
    "content": "package sh_test\n\nimport \"testing\"\n\nfunc TestFunctionsClosures(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"simpleClosure\",\n\t\t\tcode: `\n\t\t\t\tfn func(a) {\n\t\t\t\t\tfn closure() {\n\t\t\t\t\t\tprint($a)\n\t\t\t\t\t}\n\t\t\t\t\treturn $closure\n\t\t\t\t}\n\n\t\t\t\tvar x <= func(\"1\")\n\t\t\t\tvar y <= func(\"2\")\n\t\t\t\t$x()\n\t\t\t\t$y()\n\t\t\t`,\n\t\t\texpectedStdout: \"12\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"eachCallCreatesNewVar\",\n\t\t\tcode: `\n\t\t\t\tfn func() {\n\t\t\t\t\tvar a = ()\n\t\t\t\t\tfn add(elem) {\n\t\t\t\t\t\ta <= append($a, $elem)\n\t\t\t\t\t\tprint(\"a:%s,\",$a)\n\t\t\t\t\t}\n\t\t\t\t\treturn $add\n\t\t\t\t}\n\n\t\t\t\tvar add <= func()\n\t\t\t\t$add(\"1\")\n\t\t\t\t$add(\"3\")\n\t\t\t\t$add(\"5\")\n\t\t\t`,\n\t\t\texpectedStdout: \"a:1,a:1 3,a:1 3 5,\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"adder example\",\n\t\t\tcode: `\nfn makeAdder(x) {\n    fn add(y) {\n        var ret <= expr $x \"+\" $y\n        return $ret\n    }\n    return $add\n}\n\nvar add1 <= makeAdder(\"1\")\nvar add5 <= makeAdder(\"5\")\nvar add1000 <= makeAdder(\"1000\")\n\nprint(\"%s\\n\", add5(\"5\"))\nprint(\"%s\\n\", add5(\"10\"))\nprint(\"%s\\n\", add1(\"10\"))\nprint(\"%s\\n\", add1(\"2\"))\nprint(\"%s\\n\", add1000(\"50\"))\nprint(\"%s\\n\", add1000(\"100\"))\nprint(\"%s\\n\", add1(\"10\"))\n`,\n\t\t\texpectedStdout: `10\n15\n11\n3\n1050\n1100\n11\n`,\n\t\t},\n\t\t{\n\t\t\tdesc: \"logger\",\n\t\t\tcode: `fn getlogger(prefix) {\n    fn log(fmt, args...) {\n        print($prefix+$fmt+\"\\n\", $args...)\n    }\n\n    return $log\n}\n\nvar info <= getlogger(\"[info] \")\nvar error <= getlogger(\"[error] \")\nvar warn <= getlogger(\"[warn] \")\n\n$info(\"nuke initialized successfully\")\n$warn(\"temperature above anormal circunstances: %s°\", \"870\")\n$error(\"about to explode...\")\n`,\n\t\t\texpectedStdout: `[info] nuke initialized successfully\n[warn] temperature above anormal circunstances: 870°\n[error] about to explode...\n`,\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestExec(t, test)\n\t\t})\n\t}\n}\n\nfunc TestFunctionsVariables(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"fn stored only as vars\",\n\t\t\tcode: `\n\t\t\t\tfn func(a) {\n\t\t\t\t\techo -n \"hello\"\n\t\t\t\t}\n\n\t\t\t\tfunc = \"teste\"\n\t\t\t\techo -n $func\n\t\t\t\tfunc()\n\t\t\t`,\n\t\t\texpectedStdout: \"teste\",\n\t\t\texpectedErr:    \"<interactive>:8:4: Identifier 'func' is not a function\",\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestExec(t, test)\n\t\t})\n\t}\n}\n\nfunc TestFunctionsStateless(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"functions have no shared state\",\n\t\t\tcode: `fn iter(first, last, func) {\n   var sequence <= seq $first $last\n   var range <= split($sequence, \"\\n\")\n   for i in $range {\n       $func($i)\n   }\n}\n\nfn create_vm(index) {\n\techo \"create_vm: \"+$index\n\titer(\"1\", \"3\", $create_disk)\n}\n\nfn create_disk(index) {\n\techo \"create_disk: \" + $index\n}\n\niter(\"1\", \"2\", $create_vm)\n`,\n\t\t\texpectedStdout: `create_vm: 1\ncreate_disk: 1\ncreate_disk: 2\ncreate_disk: 3\ncreate_vm: 2\ncreate_disk: 1\ncreate_disk: 2\ncreate_disk: 3\n`,\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestExec(t, test)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/internal/fixture/fixture.go",
    "content": "package fixture\n\nimport (\n\t\"testing\"\n\t\"path/filepath\"\n\t\n\t\"github.com/madlambda/nash\"\n\t\"github.com/madlambda/nash/internal/testing/fixture\"\n)\n\n\ntype NashDirs struct {\n\tPath string\n\tLib string\n\tRoot string\n\tStdlib string\n\tCleanup func()\n}\n\nvar MkdirAll func(*testing.T, string) = fixture.MkdirAll\n\nvar Tmpdir func(*testing.T) (string, func()) = fixture.Tmpdir\n\nfunc SetupShell(t *testing.T) (*nash.Shell, func()) {\n\tdirs := SetupNashDirs(t)\n\t\n\tshell, err := nash.New(dirs.Path, dirs.Root)\n\n\tif err != nil {\n\t\tdirs.Cleanup()\n\t\tt.Fatal(err)\n\t}\n\n\treturn shell, dirs.Cleanup\n}\n\nfunc SetupNashDirs(t *testing.T) NashDirs {\n\ttestdir, rmdir := Tmpdir(t)\n\n\tnashpath := filepath.Join(testdir, \"nashpath\")\n\tnashroot := filepath.Join(testdir, \"nashroot\")\n\t\n\tnashlib := filepath.Join(nashpath, \"lib\")\n\tnashstdlib := filepath.Join(nashroot, \"stdlib\")\n\t\n\tMkdirAll(t, nashlib)\n\tMkdirAll(t, nashstdlib)\n\t\n\treturn NashDirs{\n\t\tPath: nashpath,\n\t\tLib: nashlib,\n\t\tRoot: nashroot,\n\t\tStdlib: nashstdlib,\n\t\tCleanup: rmdir,\n\t}\n}\n\n\n\n\n\n"
  },
  {
    "path": "internal/sh/ioutils_test.go",
    "content": "package sh_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"io/ioutil\"\n)\n\nfunc writeFile(t *testing.T, filename string, data string) {\n\terr := ioutil.WriteFile(filename, []byte(data), os.ModePerm)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc chdir(t *testing.T, dir string) {\n\tt.Helper()\n\t\n\terr := os.Chdir(dir)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc getwd(t *testing.T) string {\n\tt.Helper()\n\t\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t\n\treturn dir\n}"
  },
  {
    "path": "internal/sh/log.go",
    "content": "package sh\n\nimport (\n\t\"log\"\n\t\"os\"\n)\n\n// LogFn is the logger type\ntype LogFn func(format string, args ...interface{})\n\n// NewLog creates a new nash logger\nfunc NewLog(ns string, enable bool) LogFn {\n\tlogger := log.New(os.Stderr, \"\", 0)\n\n\treturn func(format string, args ...interface{}) {\n\t\tif enable {\n\t\t\tlogger.Printf(\"[\"+ns+\"] \"+format, args...)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/sh/rfork.go",
    "content": "// +build !linux,!plan9\n\n//\npackage sh\n\nimport (\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n)\n\nfunc (sh *Shell) executeRfork(rfork *ast.RforkNode) error {\n\treturn errors.NewError(\"rfork only supported on Linux and Plan9\")\n}\n"
  },
  {
    "path": "internal/sh/rfork_linux.go",
    "content": "// +build linux\n\n// nash provides the execution engine\npackage sh\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/madlambda/nash/ast\"\n)\n\nfunc getProcAttrs(flags uintptr) *syscall.SysProcAttr {\n\tuid := os.Getuid()\n\tgid := os.Getgid()\n\n\tsysproc := &syscall.SysProcAttr{\n\t\tCloneflags: flags,\n\t}\n\n\tif (flags & syscall.CLONE_NEWUSER) == syscall.CLONE_NEWUSER {\n\t\tsysproc.UidMappings = []syscall.SysProcIDMap{\n\t\t\t{\n\t\t\t\tContainerID: 0,\n\t\t\t\tHostID:      uid,\n\t\t\t\tSize:        1,\n\t\t\t},\n\t\t}\n\n\t\tsysproc.GidMappings = []syscall.SysProcIDMap{\n\t\t\t{\n\t\t\t\tContainerID: 0,\n\t\t\t\tHostID:      gid,\n\t\t\t\tSize:        1,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn sysproc\n}\n\nfunc dialRc(sockpath string) (net.Conn, error) {\n\tretries := 0\n\nretryRforkDial:\n\tclient, err := net.Dial(\"unix\", sockpath)\n\n\tif err != nil {\n\t\tif retries < 3 {\n\t\t\tretries++\n\t\t\ttime.Sleep(time.Duration(retries) * time.Second)\n\t\t\tgoto retryRforkDial\n\t\t}\n\t}\n\n\treturn client, err\n}\n\n// executeRfork executes the calling program again but passing\n// a new name for the process on os.Args[0] and passing an unix\n// socket file to communicate to.\nfunc (sh *Shell) executeRfork(rfork *ast.RforkNode) error {\n\tvar (\n\t\ttr               *ast.Tree\n\t\ti                int\n\t\tnashClient       net.Conn\n\t\tcopyOut, copyErr bool\n\t)\n\n\tif sh.stdout != os.Stdout {\n\t\tcopyOut = true\n\t}\n\n\tif sh.stderr != os.Stderr {\n\t\tcopyErr = true\n\t}\n\n\tif sh.nashdPath == \"\" {\n\t\treturn fmt.Errorf(\"Nashd not set\")\n\t}\n\n\tunixfile := \"/tmp/nash.\" + randRunes(4) + \".sock\"\n\n\tcmd := exec.Cmd{\n\t\tPath: sh.nashdPath,\n\t\tArgs: append([]string{\"-nashd-\"}, \"-noinit\", \"-addr\", unixfile),\n\t\tEnv:  buildenv(sh.Environ()),\n\t}\n\n\targ := rfork.Arg()\n\n\tforkFlags, err := getflags(arg.Value())\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcmd.SysProcAttr = getProcAttrs(forkFlags)\n\n\tstdoutDone := make(chan bool)\n\tstderrDone := make(chan bool)\n\n\tvar (\n\t\tstdout, stderr io.ReadCloser\n\t)\n\n\tif copyOut {\n\t\tstdout, err = cmd.StdoutPipe()\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tcmd.Stdout = sh.stdout\n\t\tclose(stdoutDone)\n\t}\n\n\tif copyErr {\n\t\tstderr, err = cmd.StderrPipe()\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tcmd.Stderr = sh.stderr\n\t\tclose(stderrDone)\n\t}\n\n\tcmd.Stdin = sh.stdin\n\n\terr = cmd.Start()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif copyOut {\n\t\tgo func() {\n\t\t\tdefer close(stdoutDone)\n\n\t\t\tio.Copy(sh.stdout, stdout)\n\t\t}()\n\t}\n\n\tif copyErr {\n\t\tgo func() {\n\t\t\tdefer close(stderrDone)\n\n\t\t\tio.Copy(sh.stderr, stderr)\n\t\t}()\n\t}\n\n\tnashClient, err = dialRc(unixfile)\n\n\tdefer nashClient.Close()\n\n\ttr = rfork.Tree()\n\n\tif tr == nil || tr.Root == nil {\n\t\treturn fmt.Errorf(\"Rfork with no sub block\")\n\t}\n\n\tfor i = 0; i < len(tr.Root.Nodes); i++ {\n\t\tvar (\n\t\t\tn, status int\n\t\t)\n\n\t\tnode := tr.Root.Nodes[i]\n\t\tdata := []byte(node.String() + \"\\n\")\n\n\t\tn, err = nashClient.Write(data)\n\n\t\tif err != nil || n != len(data) {\n\t\t\treturn fmt.Errorf(\"RPC call failed: Err: %v, bytes written: %d\", err, n)\n\t\t}\n\n\t\t// read response\n\n\t\tvar response [1024]byte\n\t\tn, err = nashClient.Read(response[:])\n\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tstatus, err = strconv.Atoi(string(response[0:n]))\n\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"Invalid status: %s\", string(response[0:n]))\n\t\t\tbreak\n\t\t}\n\n\t\tif status != 0 {\n\t\t\terr = fmt.Errorf(\"nash: Exited with status %d\", status)\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// we're done with rfork daemon\n\tnashClient.Write([]byte(\"quit\"))\n\n\t<-stdoutDone\n\t<-stderrDone\n\n\terr2 := cmd.Wait()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err2 != nil {\n\t\treturn err2\n\t}\n\n\treturn nil\n}\n\nfunc getflags(flags string) (uintptr, error) {\n\tvar (\n\t\tlflags uintptr\n\t)\n\n\tfor i := 0; i < len(flags); i++ {\n\t\tswitch flags[i] {\n\t\tcase 'c':\n\t\t\tlflags |= (syscall.CLONE_NEWUSER |\n\t\t\t\tsyscall.CLONE_NEWPID |\n\t\t\t\tsyscall.CLONE_NEWNET |\n\t\t\t\tsyscall.CLONE_NEWNS |\n\t\t\t\tsyscall.CLONE_NEWUTS |\n\t\t\t\tsyscall.CLONE_NEWIPC)\n\t\tcase 'u':\n\t\t\tlflags |= syscall.CLONE_NEWUSER\n\t\tcase 'p':\n\t\t\tlflags |= syscall.CLONE_NEWPID\n\t\tcase 'n':\n\t\t\tlflags |= syscall.CLONE_NEWNET\n\t\tcase 'm':\n\t\t\tlflags |= syscall.CLONE_NEWNS\n\t\tcase 's':\n\t\t\tlflags |= syscall.CLONE_NEWUTS\n\t\tcase 'i':\n\t\t\tlflags |= syscall.CLONE_NEWIPC\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"Wrong rfork flag: %c\", flags[i])\n\t\t}\n\t}\n\n\tif lflags == 0 {\n\t\treturn 0, fmt.Errorf(\"Rfork requires some flag\")\n\t}\n\n\treturn lflags, nil\n}\n"
  },
  {
    "path": "internal/sh/rfork_linux_test.go",
    "content": "// +build linux\n\npackage sh\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n)\n\nfunc getletters() string {\n\tvar a bytes.Buffer\n\n\tfor i := 'a'; i < 'z'; i++ {\n\t\ta.Write(append([]byte{}, byte(i)))\n\t}\n\n\tall := string(a.Bytes())\n\tallCap := strings.ToUpper(all)\n\treturn all + allCap\n}\n\nfunc getvalid() string {\n\treturn \"cumnpsi\"\n}\n\nfunc testTblFlagsOK(flagstr string, expected uintptr, t *testing.T) {\n\tflags, err := getflags(flagstr)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif flags != expected {\n\t\tt.Errorf(\"Flags differ: expected %08x but %08x\", expected, flags)\n\t\treturn\n\t}\n}\n\nfunc TestRforkFlags(t *testing.T) {\n\t_, err := getflags(\"\")\n\n\tif err == nil {\n\t\tt.Error(\"Empty flags should return error\")\n\t\treturn\n\t}\n\n\t_, err = getflags(\"a\")\n\n\tif err == nil {\n\t\tt.Error(\"Unknow flag a\")\n\t\treturn\n\t}\n\n\tallchars := getletters()\n\n\t_, err = getflags(allchars)\n\n\tif err == nil {\n\t\tt.Error(\"Should fail\")\n\t\treturn\n\t}\n\n\ttestTblFlagsOK(\"u\", syscall.CLONE_NEWUSER, t)\n\ttestTblFlagsOK(\"m\", syscall.CLONE_NEWNS, t)\n\ttestTblFlagsOK(\"n\", syscall.CLONE_NEWNET, t)\n\ttestTblFlagsOK(\"i\", syscall.CLONE_NEWIPC, t)\n\ttestTblFlagsOK(\"s\", syscall.CLONE_NEWUTS, t)\n\ttestTblFlagsOK(\"p\", syscall.CLONE_NEWPID, t)\n\ttestTblFlagsOK(\"c\", syscall.CLONE_NEWUSER|\n\t\tsyscall.CLONE_NEWNS|syscall.CLONE_NEWNET|\n\t\tsyscall.CLONE_NEWIPC|syscall.CLONE_NEWUTS|\n\t\tsyscall.CLONE_NEWUSER|syscall.CLONE_NEWPID, t)\n\ttestTblFlagsOK(\"um\", syscall.CLONE_NEWUSER|syscall.CLONE_NEWNS, t)\n\ttestTblFlagsOK(\"umn\", syscall.CLONE_NEWUSER|\n\t\tsyscall.CLONE_NEWNS|\n\t\tsyscall.CLONE_NEWNET, t)\n\ttestTblFlagsOK(\"umni\", syscall.CLONE_NEWUSER|\n\t\tsyscall.CLONE_NEWNS|\n\t\tsyscall.CLONE_NEWNET|\n\t\tsyscall.CLONE_NEWIPC, t)\n\ttestTblFlagsOK(\"umnip\", syscall.CLONE_NEWUSER|\n\t\tsyscall.CLONE_NEWNS|\n\t\tsyscall.CLONE_NEWNET|\n\t\tsyscall.CLONE_NEWIPC|\n\t\tsyscall.CLONE_NEWPID, t)\n\ttestTblFlagsOK(\"umnips\", syscall.CLONE_NEWUSER|\n\t\tsyscall.CLONE_NEWNS|\n\t\tsyscall.CLONE_NEWNET|\n\t\tsyscall.CLONE_NEWIPC|\n\t\tsyscall.CLONE_NEWPID|\n\t\tsyscall.CLONE_NEWUTS, t)\n}\n"
  },
  {
    "path": "internal/sh/rfork_plan9.go",
    "content": "// +build plan9\n\npackage sh\n\nimport (\n\t\"fmt\"\n\t\"syscall\"\n)\n\nfunc (sh *Shell) executeRfork(rfork *RforkNode) error {\n\treturn newError(\"Sorry. Plan9 rfork not implemented yet.\")\n}\n\n// getflags converts to Plan9 flags\nfunc getflags(flags string) (uintptr, error) {\n\tvar (\n\t\tpflags uintptr\n\t)\n\n\tfor i := 0; i < len(flags); i++ {\n\t\tswitch flags[i] {\n\t\tcase 'n':\n\t\t\tpflags |= syscall.RFNAMEG\n\t\tcase 'N':\n\t\t\tpflags |= syscall.RFCNAMEG\n\t\tcase 'e':\n\t\t\tpflags |= syscall.RFENVG\n\t\tcase 'E':\n\t\t\tpflags |= syscall.RFCENVG\n\t\tcase 's':\n\t\t\tpflags |= syscall.RFNOTEG\n\t\tcase 'f':\n\t\t\tpflags |= syscall.RFFDG\n\t\tcase 'F':\n\t\t\tpflags |= syscall.RFCFDG\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"Wrong rfork flag: %c\", flags[i])\n\t\t}\n\t}\n\n\tif pflags == 0 {\n\t\treturn 0, fmt.Errorf(\"Rfork requires some flag\")\n\t}\n\n\treturn pflags, nil\n}\n"
  },
  {
    "path": "internal/sh/shell.go",
    "content": "package sh\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/internal/sh/builtin\"\n\t\"github.com/madlambda/nash/parser\"\n\t\"github.com/madlambda/nash/sh\"\n\t\"github.com/madlambda/nash/token\"\n)\n\nconst (\n\tlogNS     = \"nashell.Shell\"\n\tdefPrompt = \"\\033[31mλ>\\033[0m \"\n)\n\ntype (\n\t// Env is the environment map of lists\n\tEnv map[string]sh.Obj\n\tVar Env\n\tFns map[string]sh.FnDef\n\n\tStatusCode uint8\n\n\t// Shell is the core data structure.\n\tShell struct {\n\t\tname        string\n\t\tdebug       bool\n\t\tinteractive bool\n\t\tabortOnErr  bool\n\t\tlogf        LogFn\n\t\tnashdPath   string\n\t\tisFn        bool\n\t\tfilename    string // current file being executed or imported\n\n\t\tsigs        chan os.Signal\n\t\tinterrupted bool\n\t\tlooping     bool\n\n\t\tstdin  io.Reader\n\t\tstdout io.Writer\n\t\tstderr io.Writer\n\n\t\tenv   Env\n\t\tvars  Var\n\t\tbinds Fns\n\n\t\troot   *ast.Tree\n\t\tparent *Shell\n\n\t\trepr string // string representation\n\n\t\tnashpath string\n\t\tnashroot string\n\n\t\t*sync.Mutex\n\t}\n\n\terrIgnore struct {\n\t\t*errors.NashError\n\t}\n\n\terrInterrupted struct {\n\t\t*errors.NashError\n\t}\n\n\terrStopWalking struct {\n\t\t*errors.NashError\n\t}\n)\n\nconst (\n\tESuccess    StatusCode = 0\n\tENotFound              = 127\n\tENotStarted            = 255\n)\n\nfunc newErrIgnore(format string, arg ...interface{}) error {\n\te := &errIgnore{\n\t\tNashError: errors.NewError(format, arg...),\n\t}\n\n\treturn e\n}\n\nfunc (e *errIgnore) Ignore() bool { return true }\n\nfunc newErrInterrupted(format string, arg ...interface{}) error {\n\treturn &errInterrupted{\n\t\tNashError: errors.NewError(format, arg...),\n\t}\n}\n\nfunc (e *errInterrupted) Interrupted() bool { return true }\n\nfunc newErrStopWalking() *errStopWalking {\n\treturn &errStopWalking{\n\t\tNashError: errors.NewError(\"return\"),\n\t}\n}\n\nfunc (e *errStopWalking) StopWalking() bool { return true }\n\nfunc NewAbortShell(nashpath string, nashroot string) (*Shell, error) {\n\treturn newShell(nashpath, nashroot, true)\n}\n\n// NewShell creates a new shell object\n// nashpath will be used to search libraries and nashroot will be used to\n// search for the standard library shipped with the language.\nfunc NewShell(nashpath string, nashroot string) (*Shell, error) {\n\treturn newShell(nashpath, nashroot, false)\n}\n\nfunc newShell(nashpath string, nashroot string, abort bool) (*Shell, error) {\n\tshell := &Shell{\n\t\tname:        \"parent scope\",\n\t\tinteractive: false,\n\t\tabortOnErr:  abort,\n\t\tisFn:        false,\n\t\tlogf:        NewLog(logNS, false),\n\t\tnashdPath:   nashdAutoDiscover(),\n\t\tstdout:      os.Stdout,\n\t\tstderr:      os.Stderr,\n\t\tstdin:       os.Stdin,\n\t\tenv:         make(Env),\n\t\tvars:        make(Var),\n\t\tbinds:       make(Fns),\n\t\tMutex:       &sync.Mutex{},\n\t\tsigs:        make(chan os.Signal, 1),\n\t\tfilename:    \"<interactive>\",\n\t\tnashpath:    nashpath,\n\t\tnashroot:    nashroot,\n\t}\n\n\terr := shell.setup()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tshell.setupSignals()\n\terr = validateDirs(nashpath, nashroot)\n\tif err != nil {\n\t\tif shell.abortOnErr {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tprinterr := func(msg string) {\n\t\t\tshell.Stderr().Write([]byte(msg + \"\\n\"))\n\t\t}\n\t\tprinterr(err.Error())\n\t\tprinterr(\"please check your NASHPATH and NASHROOT so they point to valid locations\")\n\t}\n\n\treturn shell, nil\n}\n\n// NewSubShell creates a nashell.Shell that inherits the parent shell stdin,\n// stdout, stderr and mutex lock.\n// Every variable and function lookup is done first in the subshell and then, if\n// not found, in the parent shell recursively.\nfunc NewSubShell(name string, parent *Shell) *Shell {\n\treturn &Shell{\n\t\tname:      name,\n\t\tisFn:      true,\n\t\tparent:    parent,\n\t\tlogf:      NewLog(logNS, false),\n\t\tnashdPath: nashdAutoDiscover(),\n\t\tstdout:    parent.Stdout(),\n\t\tstderr:    parent.Stderr(),\n\t\tstdin:     parent.Stdin(),\n\t\tenv:       make(Env),\n\t\tvars:      make(Var),\n\t\tbinds:     make(Fns),\n\t\tMutex:     parent.Mutex,\n\t\tfilename:  parent.filename,\n\t}\n}\n\nfunc (shell *Shell) NashPath() string {\n\treturn shell.nashpath\n}\n\n// initEnv creates a new environment from old one\nfunc (shell *Shell) initEnv(processEnv []string) error {\n\tlargs := make([]sh.Obj, len(os.Args))\n\n\tfor i := 0; i < len(os.Args); i++ {\n\t\tlargs[i] = sh.NewStrObj(os.Args[i])\n\t}\n\n\targv := sh.NewListObj(largs)\n\n\tshell.Setenv(\"argv\", argv)\n\tshell.Newvar(\"argv\", argv)\n\n\tfor _, penv := range processEnv {\n\t\tvar value sh.Obj\n\t\tp := strings.Split(penv, \"=\")\n\n\t\tif len(p) >= 2 {\n\t\t\t// TODO(i4k): handle lists correctly in the future\n\t\t\t// argv is not special, every list must be handled correctly\n\t\t\tif p[0] == \"argv\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvalue = sh.NewStrObj(strings.Join(p[1:], \"=\"))\n\n\t\t\tshell.Setenv(p[0], value)\n\t\t\tshell.Newvar(p[0], value)\n\t\t}\n\t}\n\n\tpidVal := sh.NewStrObj(strconv.Itoa(os.Getpid()))\n\n\tshell.Setenv(\"PID\", pidVal)\n\tshell.Newvar(\"PID\", pidVal)\n\n\tif _, ok := shell.Getenv(\"SHELL\"); !ok {\n\t\tshellVal := sh.NewStrObj(nashdAutoDiscover())\n\t\tshell.Setenv(\"SHELL\", shellVal)\n\t\tshell.Newvar(\"SHELL\", shellVal)\n\t}\n\n\tcwd, err := os.Getwd()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcwdObj := sh.NewStrObj(cwd)\n\tshell.Setenv(\"PWD\", cwdObj)\n\tshell.Newvar(\"PWD\", cwdObj)\n\n\treturn nil\n}\n\n// Reset internal state\nfunc (shell *Shell) Reset() {\n\tshell.vars = make(Var)\n\tshell.env = make(Env)\n\tshell.binds = make(Fns)\n}\n\n// SetDebug enable/disable debug in the shell\nfunc (shell *Shell) SetDebug(d bool) {\n\tshell.debug = d\n\tshell.logf = NewLog(logNS, d)\n}\n\n// SetInteractive enable/disable shell interactive mode\nfunc (shell *Shell) SetInteractive(i bool) {\n\tshell.interactive = i\n\n\tif i {\n\t\t_ = shell.setupDefaultBindings()\n\t}\n}\n\nfunc (shell *Shell) Interactive() bool {\n\tif shell.parent != nil {\n\t\treturn shell.parent.Interactive()\n\t}\n\n\treturn shell.interactive\n}\n\nfunc (shell *Shell) SetName(a string) {\n\tshell.name = a\n}\n\nfunc (shell *Shell) Name() string { return shell.name }\n\nfunc (shell *Shell) SetParent(a *Shell) {\n\tshell.parent = a\n}\n\nfunc (shell *Shell) Environ() Env {\n\tif shell.parent != nil {\n\t\treturn shell.parent.Environ()\n\t}\n\n\treturn shell.env\n}\n\nfunc (shell *Shell) Getenv(name string) (sh.Obj, bool) {\n\tif shell.parent != nil {\n\t\treturn shell.parent.Getenv(name)\n\t}\n\n\tvalue, ok := shell.env[name]\n\treturn value, ok\n}\n\nfunc (shell *Shell) Setenv(name string, value sh.Obj) {\n\tif shell.parent != nil {\n\t\tshell.parent.Setenv(name, value)\n\t\treturn\n\t}\n\n\tshell.Newvar(name, value)\n\n\tshell.env[name] = value\n\tos.Setenv(name, value.String())\n}\n\nfunc (shell *Shell) SetEnviron(processEnv []string) {\n\tshell.env = make(Env)\n\n\tfor _, penv := range processEnv {\n\t\tvar value sh.Obj\n\t\tp := strings.Split(penv, \"=\")\n\n\t\tif len(p) == 2 {\n\t\t\tvalue = sh.NewStrObj(p[1])\n\n\t\t\tshell.Setenv(p[0], value)\n\t\t\tshell.Newvar(p[0], value)\n\t\t}\n\t}\n}\n\n// GetLocalvar returns a local scoped variable.\nfunc (shell *Shell) GetLocalvar(name string) (sh.Obj, bool) {\n\tv, ok := shell.vars[name]\n\treturn v, ok\n}\n\n// Getvar returns the value of the variable name. It could look in their\n// parent scopes if not found locally.\nfunc (shell *Shell) Getvar(name string) (sh.Obj, bool) {\n\tif value, ok := shell.vars[name]; ok {\n\t\treturn value, ok\n\t}\n\n\tif shell.parent != nil {\n\t\treturn shell.parent.Getvar(name)\n\t}\n\n\treturn nil, false\n}\n\n// GetFn returns the function name or error if not found.\nfunc (shell *Shell) GetFn(name string) (*sh.FnObj, error) {\n\tshell.logf(\"Looking for function '%s' on shell '%s'\\n\", name, shell.name)\n\tif obj, ok := shell.vars[name]; ok {\n\t\tif obj.Type() == sh.FnType {\n\t\t\tfnObj := obj.(*sh.FnObj)\n\t\t\treturn fnObj, nil\n\t\t}\n\t\treturn nil, errors.NewError(\"Identifier '%s' is not a function\", name)\n\t}\n\n\tif shell.parent != nil {\n\t\treturn shell.parent.GetFn(name)\n\t}\n\n\treturn nil, fmt.Errorf(\"function '%s' not found\", name)\n}\n\nfunc (shell *Shell) Setbindfn(name string, value sh.FnDef) {\n\tshell.binds[name] = value\n}\n\nfunc (shell *Shell) Getbindfn(cmdName string) (sh.FnDef, bool) {\n\tif fn, ok := shell.binds[cmdName]; ok {\n\t\treturn fn, true\n\t}\n\n\tif shell.parent != nil {\n\t\treturn shell.parent.Getbindfn(cmdName)\n\t}\n\n\treturn nil, false\n}\n\n// Newvar creates a new variable in the scope.\nfunc (shell *Shell) Newvar(name string, value sh.Obj) {\n\tshell.vars[name] = value\n}\n\n// Setvar updates the value of an existing variable. If the variable\n// doesn't exist in current scope it looks up in their parent scopes.\n// It returns true if the variable was found and updated.\nfunc (shell *Shell) Setvar(name string, value sh.Obj) bool {\n\t_, ok := shell.vars[name]\n\tif ok {\n\t\tshell.vars[name] = value\n\t\treturn true\n\t}\n\n\tif shell.parent != nil {\n\t\treturn shell.parent.Setvar(name, value)\n\t}\n\n\treturn false\n}\n\nfunc (shell *Shell) IsFn() bool     { return shell.isFn }\nfunc (shell *Shell) SetIsFn(b bool) { shell.isFn = b }\n\n// SetNashdPath sets an alternativa path to nashd\nfunc (shell *Shell) SetNashdPath(path string) {\n\tshell.nashdPath = path\n}\n\n// SetStdin sets the stdin for commands\nfunc (shell *Shell) SetStdin(in io.Reader) {\n\tshell.stdin = in\n}\n\n// SetStdout sets stdout for commands\nfunc (shell *Shell) SetStdout(out io.Writer) {\n\tshell.stdout = out\n}\n\n// SetStderr sets stderr for commands\nfunc (shell *Shell) SetStderr(err io.Writer) {\n\tshell.stderr = err\n}\n\nfunc (shell *Shell) Stdout() io.Writer { return shell.stdout }\nfunc (shell *Shell) Stderr() io.Writer { return shell.stderr }\nfunc (shell *Shell) Stdin() io.Reader  { return shell.stdin }\n\n// SetTree sets the internal tree of the interpreter. It's used for\n// sub-shells like `fn`.\nfunc (shell *Shell) SetTree(t *ast.Tree) {\n\tshell.root = t\n}\n\n// Tree returns the internal tree of the subshell.\nfunc (shell *Shell) Tree() *ast.Tree { return shell.root }\n\n// SetRepr set the string representation of the shell\nfunc (shell *Shell) SetRepr(a string) {\n\tshell.repr = a\n}\n\nfunc (shell *Shell) setupBuiltin() {\n\tfor name, constructor := range builtin.Constructors() {\n\t\tfnDef := newBuiltinFnDef(name, shell, constructor)\n\t\tshell.Newvar(name, sh.NewFnObj(fnDef))\n\t}\n}\n\nfunc (shell *Shell) setupDefaultBindings() error {\n\t// only one builtin fn... no need for advanced machinery yet\n\thomeEnvVar := \"HOME\"\n\tif runtime.GOOS == \"windows\" {\n\t\thomeEnvVar = \"HOMEPATH\"\n\t}\n\terr := shell.Exec(shell.name, fmt.Sprintf(`fn nash_builtin_cd(args...) {\n\t    var path = \"\"\n\t    var l <= len($args)\n            if $l == \"0\" {\n                    path = $%s\n            } else {\n                    path = $args[0]\n\t    }\n\n            chdir($path)\n        }\n\n        bindfn nash_builtin_cd cd`, homeEnvVar))\n\n\treturn err\n}\n\nfunc (shell *Shell) setup() error {\n\terr := shell.initEnv(os.Environ())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif shell.env[\"PROMPT\"] == nil {\n\t\tpobj := sh.NewStrObj(defPrompt)\n\t\tshell.Setenv(\"PROMPT\", pobj)\n\t\tshell.Newvar(\"PROMPT\", pobj)\n\t}\n\n\t_, ok := shell.Getvar(\"_\")\n\tif !ok {\n\t\tshell.Newvar(\"_\", sh.NewStrObj(\"\"))\n\t}\n\n\tshell.setupBuiltin()\n\treturn err\n}\n\nfunc (shell *Shell) setupSignals() {\n\tsignal.Notify(shell.sigs, syscall.SIGINT)\n\n\tgo func() {\n\t\tfor {\n\t\t\tsig := <-shell.sigs\n\n\t\t\tswitch sig {\n\t\t\tcase syscall.SIGINT:\n\t\t\t\tshell.Lock()\n\n\t\t\t\t// TODO(i4k): Review implementation when interrupted inside\n\t\t\t\t// function loops\n\t\t\t\tif shell.looping {\n\t\t\t\t\tshell.setIntr(true)\n\t\t\t\t}\n\n\t\t\t\tshell.Unlock()\n\t\t\tdefault:\n\t\t\t\tfmt.Printf(\"%s\\n\", sig)\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// TriggerCTRLC mock the user pressing CTRL-C in the terminal\nfunc (shell *Shell) TriggerCTRLC() error {\n\tp, err := os.FindProcess(os.Getpid())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn p.Signal(syscall.SIGINT)\n}\n\n// setIntr *do not lock*. You must do it yourself!\nfunc (shell *Shell) setIntr(b bool) {\n\tif shell.parent != nil {\n\t\tshell.parent.setIntr(b)\n\t\treturn\n\t}\n\n\tshell.interrupted = b\n}\n\n// getIntr returns true if nash was interrupted by CTRL-C\nfunc (shell *Shell) getIntr() bool {\n\tif shell.parent != nil {\n\t\treturn shell.parent.getIntr()\n\t}\n\n\treturn shell.interrupted\n}\n\n// Exec executes the commands specified by string content\nfunc (shell *Shell) Exec(path, content string) error {\n\tp := parser.NewParser(path, content)\n\n\ttr, err := p.Parse()\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = shell.ExecuteTree(tr)\n\treturn err\n}\n\n// Execute the nash file at given path\nfunc (shell *Shell) ExecFile(path string) error {\n\tbkCurFile := shell.filename\n\n\tcontent, err := ioutil.ReadFile(path)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tshell.filename = path\n\n\tdefer func() {\n\t\tshell.filename = bkCurFile\n\t}()\n\n\treturn shell.Exec(path, string(content))\n}\n\nfunc (shell *Shell) newvar(name *ast.NameNode, value sh.Obj) error {\n\tif name.Index == nil {\n\t\tshell.Newvar(name.Ident, value)\n\t\treturn nil\n\t}\n\n\t// handles ident[x] = v\n\n\tobj, ok := shell.Getvar(name.Ident)\n\tif !ok {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tname, \"Variable %s not found\", name.Ident)\n\t}\n\n\tindex, err := shell.evalIndex(name.Index)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcol, err := sh.NewWriteableCollection(obj)\n\tif err != nil {\n\t\treturn errors.NewEvalError(shell.filename, name, err.Error())\n\t}\n\n\terr = col.Set(index, value)\n\tif err != nil {\n\t\treturn errors.NewEvalError(\n\t\t\tshell.filename,\n\t\t\tname,\n\t\t\t\"error[%s] setting var\",\n\t\t\terr,\n\t\t)\n\t}\n\n\tshell.Newvar(name.Ident, obj)\n\treturn nil\n}\n\nfunc (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error {\n\tif name.Index == nil {\n\t\tif !shell.Setvar(name.Ident, value) {\n\t\t\treturn errors.NewEvalError(shell.filename,\n\t\t\t\tname, \"Variable '%s' is not initialized. Use 'var %s = <value>'\",\n\t\t\t\tname, name)\n\t\t}\n\t\treturn nil\n\t}\n\n\tobj, ok := shell.Getvar(name.Ident)\n\tif !ok {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tname, \"Variable %s not found\", name.Ident)\n\t}\n\n\tindex, err := shell.evalIndex(name.Index)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcol, err := sh.NewWriteableCollection(obj)\n\tif err != nil {\n\t\treturn errors.NewEvalError(shell.filename, name, err.Error())\n\t}\n\n\terr = col.Set(index, value)\n\tif err != nil {\n\t\treturn errors.NewEvalError(\n\t\t\tshell.filename,\n\t\t\tname,\n\t\t\t\"error[%s] setting var\",\n\t\t\terr,\n\t\t)\n\t}\n\n\tif !shell.Setvar(name.Ident, obj) {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tname, \"Variable '%s' is not initialized. Use 'var %s = <value>'\",\n\t\t\tname, name)\n\t}\n\treturn nil\n}\n\nfunc (shell *Shell) setvars(names []*ast.NameNode, values []sh.Obj) error {\n\tfor i := 0; i < len(names); i++ {\n\t\terr := shell.setvar(names[i], values[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (shell *Shell) newvars(names []*ast.NameNode, values []sh.Obj) {\n\tfor i := 0; i < len(names); i++ {\n\t\tshell.newvar(names[i], values[i])\n\t}\n}\n\nfunc (shell *Shell) setcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) error {\n\tif len(names) == 3 {\n\t\terr := shell.setvar(names[0], stdout)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = shell.setvar(names[1], stderr)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn shell.setvar(names[2], status)\n\t} else if len(names) == 2 {\n\t\terr := shell.setvar(names[0], stdout)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn shell.setvar(names[1], status)\n\t} else if len(names) == 1 {\n\t\treturn shell.setvar(names[0], stdout)\n\t}\n\n\tpanic(fmt.Sprintf(\"internal error: expects 1 <= len(names) <= 3,\"+\n\t\t\" but got %d\",\n\t\tlen(names)))\n\n\treturn nil\n}\n\nfunc (shell *Shell) newcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) {\n\tif len(names) == 3 {\n\t\tshell.newvar(names[0], stdout)\n\t\tshell.newvar(names[1], stderr)\n\t\tshell.newvar(names[2], status)\n\t} else if len(names) == 2 {\n\t\tshell.newvar(names[0], stdout)\n\t\tshell.newvar(names[1], status)\n\t} else if len(names) == 1 {\n\t\tshell.newvar(names[0], stdout)\n\t} else {\n\t\tpanic(fmt.Sprintf(\"internal error: expects 1 <= len(names) <= 3,\"+\n\t\t\t\" but got %d\",\n\t\t\tlen(names)))\n\t}\n}\n\n// evalConcat reveives the AST representation of a concatenation of objects and\n// returns the string representation, or error.\nfunc (shell *Shell) evalConcat(path ast.Expr) (string, error) {\n\tvar pathStr string\n\n\tif path.Type() != ast.NodeConcatExpr {\n\t\treturn \"\", fmt.Errorf(\"Invalid node %+v\", path)\n\t}\n\n\tconcatExpr := path.(*ast.ConcatExpr)\n\tconcat := concatExpr.List()\n\n\tfor i := 0; i < len(concat); i++ {\n\t\tpart := concat[i]\n\n\t\tswitch part.Type() {\n\t\tcase ast.NodeConcatExpr:\n\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\"Nested concat is not allowed: %s\", part)\n\t\tcase ast.NodeVarExpr, ast.NodeIndexExpr:\n\t\t\tpartValue, err := shell.evalVariable(part)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\n\t\t\tif partValue.Type() == sh.ListType {\n\t\t\t\treturn \"\", errors.NewEvalError(shell.filename,\n\t\t\t\t\tpart, \"Concat of list variables is not allowed: %v = %v\",\n\t\t\t\t\tpart, partValue)\n\t\t\t} else if partValue.Type() != sh.StringType {\n\t\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\t\"Invalid concat element: %v\", partValue)\n\t\t\t}\n\n\t\t\tstrval := partValue.(*sh.StrObj)\n\t\t\tpathStr += strval.Str()\n\t\tcase ast.NodeStringExpr:\n\t\t\tstr, ok := part.(*ast.StringExpr)\n\t\t\tif !ok {\n\t\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\t\"Failed to eval string: %s\", part)\n\t\t\t}\n\n\t\t\tpathStr += str.Value()\n\t\tcase ast.NodeFnInv:\n\t\t\tfnNode := part.(*ast.FnInvNode)\n\t\t\tresult, err := shell.executeFnInv(fnNode)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\n\t\t\tif len(result) == 0 || len(result) > 1 {\n\t\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\t\"Function '%s' used in string concat but returns %d values.\",\n\t\t\t\t\tfnNode.Name)\n\t\t\t}\n\t\t\tobj := result[0]\n\t\t\tif obj.Type() != sh.StringType {\n\t\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\t\"Function '%s' used in concat but returns a '%s'\", obj.Type())\n\t\t\t}\n\n\t\t\tstr := obj.(*sh.StrObj)\n\t\t\tpathStr += str.Str()\n\t\tcase ast.NodeListExpr:\n\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\"Concat of lists is not allowed: %+v\", part.String())\n\t\tdefault:\n\t\t\treturn \"\", errors.NewEvalError(shell.filename, part,\n\t\t\t\t\"Invalid argument: %+v\", part)\n\t\t}\n\t}\n\n\treturn pathStr, nil\n}\n\nfunc (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) {\n\tvar (\n\t\tobjs []sh.Obj\n\t\terr  error\n\t)\n\n\tshell.logf(\"Executing node: %v\\n\", node)\n\n\tswitch node.Type() {\n\tcase ast.NodeImport:\n\t\terr = shell.executeImport(node.(*ast.ImportNode))\n\tcase ast.NodeComment:\n\t\t// ignore\n\tcase ast.NodeSetenv:\n\t\terr = shell.executeSetenv(node.(*ast.SetenvNode))\n\tcase ast.NodeVarAssignDecl:\n\t\terr = shell.executeVarAssign(node.(*ast.VarAssignDeclNode))\n\tcase ast.NodeVarExecAssignDecl:\n\t\terr = shell.executeVarExecAssign(node.(*ast.VarExecAssignDeclNode))\n\tcase ast.NodeAssign:\n\t\terr = shell.executeAssignment(node.(*ast.AssignNode))\n\tcase ast.NodeExecAssign:\n\t\terr = shell.executeExecAssign(node.(*ast.ExecAssignNode))\n\tcase ast.NodeCommand:\n\t\t_, err = shell.executeCommand(node.(*ast.CommandNode))\n\tcase ast.NodePipe:\n\t\t_, err = shell.executePipe(node.(*ast.PipeNode))\n\tcase ast.NodeRfork:\n\t\terr = shell.executeRfork(node.(*ast.RforkNode))\n\tcase ast.NodeIf:\n\t\tobjs, err = shell.executeIf(node.(*ast.IfNode))\n\tcase ast.NodeFnDecl:\n\t\terr = shell.executeFnDecl(node.(*ast.FnDeclNode))\n\tcase ast.NodeFnInv:\n\t\t// invocation ignoring output\n\t\t_, err = shell.executeFnInv(node.(*ast.FnInvNode))\n\tcase ast.NodeFor:\n\t\tobjs, err = shell.executeFor(node.(*ast.ForNode))\n\tcase ast.NodeBindFn:\n\t\terr = shell.executeBindFn(node.(*ast.BindFnNode))\n\tcase ast.NodeReturn:\n\t\tif shell.IsFn() {\n\t\t\tobjs, err = shell.executeReturn(node.(*ast.ReturnNode))\n\t\t} else {\n\t\t\terr = errors.NewEvalError(shell.filename,\n\t\t\t\tnode,\n\t\t\t\t\"Unexpected return outside of function declaration.\")\n\t\t}\n\tdefault:\n\t\t// should never get here\n\t\treturn nil, errors.NewEvalError(shell.filename, node,\n\t\t\t\"invalid node: %v.\", node.Type())\n\t}\n\n\treturn objs, err\n}\n\nfunc (shell *Shell) ExecuteTree(tr *ast.Tree) ([]sh.Obj, error) {\n\treturn shell.executeTree(tr, true)\n}\n\n// executeTree evaluates the given tree\nfunc (shell *Shell) executeTree(tr *ast.Tree, stopable bool) ([]sh.Obj, error) {\n\tif tr == nil || tr.Root == nil {\n\t\treturn nil, errors.NewError(\"empty abstract syntax tree to execute\")\n\t}\n\n\troot := tr.Root\n\n\tfor _, node := range root.Nodes {\n\t\tobjs, err := shell.executeNode(node)\n\t\tif err != nil {\n\t\t\ttype (\n\t\t\t\tIgnoreError interface {\n\t\t\t\t\tIgnore() bool\n\t\t\t\t}\n\n\t\t\t\tInterruptedError interface {\n\t\t\t\t\tInterrupted() bool\n\t\t\t\t}\n\n\t\t\t\tStopWalkingError interface {\n\t\t\t\t\tStopWalking() bool\n\t\t\t\t}\n\t\t\t)\n\n\t\t\tif errIgnore, ok := err.(IgnoreError); ok && errIgnore.Ignore() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif errInterrupted, ok := err.(InterruptedError); ok && errInterrupted.Interrupted() {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif errStopWalking, ok := err.(StopWalkingError); stopable && ok && errStopWalking.StopWalking() {\n\t\t\t\treturn objs, nil\n\t\t\t}\n\n\t\t\treturn objs, err\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\nfunc (shell *Shell) executeReturn(n *ast.ReturnNode) ([]sh.Obj, error) {\n\tvar returns []sh.Obj\n\n\treturnExprs := n.Returns\n\n\tfor i := 0; i < len(returnExprs); i++ {\n\t\tretExpr := returnExprs[i]\n\n\t\tobj, err := shell.evalExpr(retExpr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturns = append(returns, obj)\n\t}\n\n\treturn returns, newErrStopWalking()\n}\n\nfunc (shell *Shell) getNashRootFromGOPATH(preverr error) (string, error) {\n\tg, hasgopath := shell.Getenv(\"GOPATH\")\n\tif !hasgopath {\n\t\treturn \"\", errors.NewError(\"%s\\nno GOPATH env var setted\", preverr)\n\t}\n\tgopath := g.String()\n\treturn filepath.Join(gopath, filepath.FromSlash(\"/src/github.com/madlambda/nash\")), nil\n}\n\nfunc isValidNashRoot(nashroot string) bool {\n\t_, err := os.Stat(filepath.Join(nashroot, \"stdlib\"))\n\treturn err == nil\n}\n\nfunc (shell *Shell) executeImport(node *ast.ImportNode) error {\n\tobj, err := shell.evalExpr(node.Path)\n\tif err != nil {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tnode, err.Error())\n\t}\n\n\tif obj.Type() != sh.StringType {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tnode.Path,\n\t\t\t\"Invalid type on import argument: %s\", obj.Type())\n\t}\n\n\tobjstr := obj.(*sh.StrObj)\n\tfname := objstr.Str()\n\n\tshell.logf(\"Importing '%s'\", fname)\n\n\tvar (\n\t\ttries  []string\n\t\thasExt bool\n\t)\n\n\thasExt = filepath.Ext(fname) != \"\"\n\tif filepath.IsAbs(fname) {\n\t\ttries = append(tries, fname)\n\n\t\tif !hasExt {\n\t\t\ttries = append(tries, fname+\".sh\")\n\t\t}\n\t}\n\n\tif shell.filename != \"\" {\n\t\tlocalFile := filepath.Join(filepath.Dir(shell.filename), fname)\n\t\ttries = append(tries, localFile)\n\n\t\tif !hasExt {\n\t\t\ttries = append(tries, localFile+\".sh\")\n\t\t}\n\t}\n\n\ttries = append(tries, filepath.Join(shell.nashpath, \"lib\", fname))\n\tif !hasExt {\n\t\ttries = append(tries, filepath.Join(shell.nashpath, \"lib\", fname+\".sh\"))\n\t}\n\n\ttries = append(tries, filepath.Join(shell.nashroot, \"stdlib\", fname+\".sh\"))\n\n\tshell.logf(\"Trying %q\\n\", tries)\n\n\tfor _, path := range tries {\n\t\td, err := os.Stat(path)\n\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif m := d.Mode(); !m.IsDir() {\n\t\t\treturn shell.ExecFile(path)\n\t\t}\n\t}\n\n\terrmsg := fmt.Sprintf(\n\t\t\"Failed to import path '%s'. The locations below have been tried:\\n \\\"%s\\\"\",\n\t\tfname,\n\t\tstrings.Join(tries, `\", \"`),\n\t)\n\n\treturn errors.NewEvalError(shell.filename, node, errmsg)\n}\n\n// executePipe executes a pipe of ast.Command's. Each command can be\n// a path command in the operating system or a function bind to a\n// command name.\n// The error of each command can be suppressed prepending it with '-' (dash).\n// The error returned will be a string representing the errors (or none) of\n// each command separated by '|'. The $status of pipe execution will be\n// the $status of each command separated by '|'.\nfunc (shell *Shell) executePipe(pipe *ast.PipeNode) (sh.Obj, error) {\n\tvar (\n\t\tcloseFiles     []io.Closer\n\t\tcloseAfterWait []io.Closer\n\t\terrIndex       int\n\t\terr            error\n\t)\n\n\tdefer func() {\n\t\tfor _, c := range closeAfterWait {\n\t\t\tc.Close()\n\t\t}\n\t}()\n\n\tnodeCommands := pipe.Commands()\n\n\tif len(nodeCommands) < 2 {\n\t\treturn sh.NewStrObj(strconv.Itoa(ENotStarted)),\n\t\t\terrors.NewEvalError(shell.filename,\n\t\t\t\tpipe, \"Pipe requires at least two commands.\")\n\t}\n\n\tcmds := make([]sh.Runner, len(nodeCommands))\n\terrs := make([]string, len(nodeCommands))\n\tigns := make([]bool, len(nodeCommands)) // ignoreErrors\n\tcods := make([]string, len(nodeCommands))\n\n\tfor i := 0; i < len(nodeCommands); i++ {\n\t\terrs[i] = \"not started\"\n\t\tcods[i] = strconv.Itoa(ENotStarted)\n\t}\n\n\tlast := len(nodeCommands) - 1\n\n\tenvVars := buildenv(shell.Environ())\n\n\t// Create all commands\n\tfor i := 0; i < len(nodeCommands); i++ {\n\t\tvar (\n\t\t\tcmd    sh.Runner\n\t\t\tignore bool\n\t\t\targs   []sh.Obj\n\t\t)\n\n\t\tnodeCmd := nodeCommands[i]\n\n\t\tcmd, ignore, err = shell.getCommand(nodeCmd)\n\n\t\tigns[i] = ignore\n\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tcods[i] = strconv.Itoa(ENotFound)\n\t\t\tgoto pipeError\n\t\t}\n\n\t\t// SetEnviron must be called before SetArgs\n\t\t// otherwise the subshell will have the arguments\n\t\t// shadowed by parent env\n\t\tcmd.SetEnviron(envVars)\n\t\targs, err = shell.evalExprs(nodeCmd.Args())\n\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tgoto pipeError\n\t\t}\n\n\t\terr = cmd.SetArgs(args)\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tgoto pipeError\n\t\t}\n\n\t\tcmd.SetStdin(shell.stdin)\n\n\t\tif i < last {\n\t\t\tcloseFiles, err = shell.setRedirects(cmd, nodeCmd.Redirects())\n\t\t\tcloseAfterWait = append(closeAfterWait, closeFiles...)\n\n\t\t\tif err != nil {\n\t\t\t\terrIndex = i\n\t\t\t\tgoto pipeError\n\t\t\t}\n\t\t}\n\n\t\tcmds[i] = cmd\n\t}\n\n\t// Shell does not support stdin redirection yet\n\tcmds[0].SetStdin(shell.stdin)\n\n\t// Setup the commands. Pointing the stdin of next command to stdout of previous.\n\t// Except the stdout of last one\n\tfor i, cmd := range cmds[:last] {\n\t\tvar (\n\t\t\tstdin io.ReadCloser\n\t\t)\n\n\t\t// StdoutPipe complains if Stdout is already set\n\t\tcmd.SetStdout(nil)\n\t\tstdin, err = cmd.StdoutPipe()\n\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tgoto pipeError\n\t\t}\n\n\t\tcmds[i+1].SetStdin(stdin)\n\t}\n\n\tcmds[last].SetStdout(shell.stdout)\n\tcmds[last].SetStderr(shell.stderr)\n\n\tcloseFiles, err = shell.setRedirects(cmds[last], nodeCommands[last].Redirects())\n\tcloseAfterWait = append(closeAfterWait, closeFiles...)\n\n\tif err != nil {\n\t\terrIndex = last\n\t\tgoto pipeError\n\t}\n\n\tfor i := 0; i < len(cmds); i++ {\n\t\tcmd := cmds[i]\n\n\t\terr = cmd.Start()\n\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tgoto pipeError\n\t\t}\n\n\t\terrs[i] = \"success\"\n\t\tcods[i] = \"0\"\n\t}\n\n\tfor i, cmd := range cmds {\n\t\terr = cmd.Wait()\n\n\t\tif err != nil {\n\t\t\terrIndex = i\n\t\t\tgoto pipeError\n\t\t}\n\n\t\terrs[i] = \"success\"\n\t\tcods[i] = \"0\"\n\t}\n\n\treturn sh.NewStrObj(\"0\"), nil\n\npipeError:\n\tif igns[errIndex] {\n\t\terrs[errIndex] = \"none\"\n\t} else {\n\t\terrs[errIndex] = err.Error()\n\t}\n\n\tcods[errIndex] = getErrStatus(err, cods[errIndex])\n\n\terr = errors.NewEvalError(shell.filename,\n\t\tpipe, strings.Join(errs, \"|\"))\n\n\t// verify if all status codes are the same\n\tuniqCodes := make(map[string]struct{})\n\tvar uniqCode string\n\n\tfor i := 0; i < len(cods); i++ {\n\t\tuniqCodes[cods[i]] = struct{}{}\n\t\tuniqCode = cods[i]\n\t}\n\n\tvar status sh.Obj\n\n\tif len(uniqCodes) == 1 {\n\t\t// if all status are the same\n\t\tstatus = sh.NewStrObj(uniqCode)\n\t} else {\n\t\tstatus = sh.NewStrObj(strings.Join(cods, \"|\"))\n\t}\n\n\tif igns[errIndex] {\n\t\treturn status, nil\n\t}\n\n\treturn status, err\n}\n\nfunc (shell *Shell) openRedirectLocation(location ast.Expr) (io.WriteCloser, error) {\n\tvar protocol string\n\n\tlocationObj, err := shell.evalExpr(location)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif locationObj.Type() != sh.StringType {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tlocation,\n\t\t\t\"Redirection to invalid object type: %v (%s)\", locationObj, locationObj.Type())\n\t}\n\n\tobjstr := locationObj.(*sh.StrObj)\n\tlocationStr := objstr.Str()\n\n\tif len(locationStr) > 6 {\n\t\tif locationStr[0:6] == \"tcp://\" {\n\t\t\tprotocol = \"tcp\"\n\t\t} else if locationStr[0:6] == \"udp://\" {\n\t\t\tprotocol = \"udp\"\n\t\t} else if len(locationStr) > 7 && locationStr[0:7] == \"unix://\" {\n\t\t\tprotocol = \"unix\"\n\t\t}\n\t}\n\n\tif protocol == \"\" {\n\t\treturn os.OpenFile(locationStr, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)\n\t}\n\n\tswitch protocol {\n\tcase \"tcp\", \"udp\":\n\t\tnetParts := strings.Split(locationStr[6:], \":\")\n\n\t\tif len(netParts) != 2 {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tlocation,\n\t\t\t\t\"Invalid tcp/udp address: %s\", locationStr)\n\t\t}\n\n\t\turl := netParts[0] + \":\" + netParts[1]\n\n\t\treturn net.Dial(protocol, url)\n\tcase \"unix\":\n\t\treturn net.Dial(protocol, locationStr[7:])\n\t}\n\n\treturn nil, errors.NewEvalError(shell.filename, location,\n\t\t\"Unexpected redirection value: %s\", locationStr)\n}\n\nfunc (shell *Shell) setRedirects(cmd sh.Runner, redirDecls []*ast.RedirectNode) ([]io.Closer, error) {\n\tvar closeAfterWait []io.Closer\n\n\tfor _, r := range redirDecls {\n\t\tcloseFiles, err := shell.buildRedirect(cmd, r)\n\t\tcloseAfterWait = append(closeAfterWait, closeFiles...)\n\n\t\tif err != nil {\n\t\t\treturn closeAfterWait, err\n\t\t}\n\t}\n\n\treturn closeAfterWait, nil\n}\n\nfunc (shell *Shell) buildRedirect(cmd sh.Runner, redirDecl *ast.RedirectNode) ([]io.Closer, error) {\n\tvar closeAfterWait []io.Closer\n\n\tif redirDecl.LeftFD() > 2 || redirDecl.LeftFD() < ast.RedirMapSupress {\n\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\tredirDecl,\n\t\t\t\"Invalid file descriptor redirection: fd=%d\", redirDecl.LeftFD())\n\t}\n\n\tif redirDecl.RightFD() > 2 || redirDecl.RightFD() < ast.RedirMapSupress {\n\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\tredirDecl,\n\t\t\t\"Invalid file descriptor redirection: fd=%d\", redirDecl.RightFD())\n\t}\n\n\tvar err error\n\n\t// Note(i4k): We need to remove the repetitive code in some smarter way\n\tswitch redirDecl.LeftFD() {\n\tcase 0:\n\t\treturn closeAfterWait, fmt.Errorf(\"Does not support stdin redirection yet\")\n\tcase 1:\n\t\tswitch redirDecl.RightFD() {\n\t\tcase 0:\n\t\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\t\tredirDecl,\n\t\t\t\t\"Invalid redirect mapping: %d -> %d\", 1, 0)\n\t\tcase 1: // do nothing\n\t\tcase 2:\n\t\t\tcmd.SetStdout(cmd.Stderr())\n\t\tcase ast.RedirMapNoValue:\n\t\t\tif redirDecl.Location() == nil {\n\t\t\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\t\t\tredirDecl,\n\t\t\t\t\t\"Missing file in redirection: >[%d] <??>\", redirDecl.LeftFD())\n\t\t\t}\n\n\t\t\tfile, err := shell.openRedirectLocation(redirDecl.Location())\n\t\t\tif err != nil {\n\t\t\t\treturn closeAfterWait, err\n\t\t\t}\n\n\t\t\tcmd.SetStdout(file)\n\t\t\tcloseAfterWait = append(closeAfterWait, file)\n\t\tcase ast.RedirMapSupress:\n\t\t\tfile := ioutil.Discard\n\t\t\tcmd.SetStdout(file)\n\t\t}\n\tcase 2:\n\t\tswitch redirDecl.RightFD() {\n\t\tcase 0:\n\t\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\t\tredirDecl, \"Invalid redirect mapping: %d -> %d\", 2, 1)\n\t\tcase 1:\n\t\t\tcmd.SetStderr(cmd.Stdout())\n\t\tcase 2: // do nothing\n\t\tcase ast.RedirMapNoValue:\n\t\t\tif redirDecl.Location() == nil {\n\t\t\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\t\t\tredirDecl,\n\t\t\t\t\t\"Missing file in redirection: >[%d] <??>\", redirDecl.LeftFD())\n\t\t\t}\n\n\t\t\tfile, err := shell.openRedirectLocation(redirDecl.Location())\n\t\t\tif err != nil {\n\t\t\t\treturn closeAfterWait, err\n\t\t\t}\n\n\t\t\tcmd.SetStderr(file)\n\t\t\tcloseAfterWait = append(closeAfterWait, file)\n\t\tcase ast.RedirMapSupress:\n\t\t\tcmd.SetStderr(ioutil.Discard)\n\t\t}\n\tcase ast.RedirMapNoValue:\n\t\tif redirDecl.Location() == nil {\n\t\t\treturn closeAfterWait, errors.NewEvalError(shell.filename,\n\t\t\t\tredirDecl, \"Missing file in redirection: >[%d] <??>\", redirDecl.LeftFD())\n\t\t}\n\n\t\tfile, err := shell.openRedirectLocation(redirDecl.Location())\n\t\tif err != nil {\n\t\t\treturn closeAfterWait, err\n\t\t}\n\n\t\tcmd.SetStdout(file)\n\t\tcloseAfterWait = append(closeAfterWait, file)\n\t}\n\n\treturn closeAfterWait, err\n}\n\nfunc (shell *Shell) newBindfnRunner(\n\tc *ast.CommandNode,\n\tcmdName string,\n\tfnDef sh.FnDef,\n) (sh.Runner, error) {\n\tshell.logf(\"Executing bind %s\", cmdName)\n\tshell.logf(\"%s bind to %s\", cmdName, fnDef.Name())\n\n\tif !shell.Interactive() {\n\t\terr := errors.NewEvalError(shell.filename,\n\t\t\tc, \"'%s' is a bind to '%s'. \"+\n\t\t\t\t\"No binds allowed in non-interactive mode.\",\n\t\t\tcmdName,\n\t\t\tfnDef.Name())\n\t\treturn nil, err\n\t}\n\n\treturn fnDef.Build(), nil\n}\n\nfunc (shell *Shell) getCommand(c *ast.CommandNode) (sh.Runner, bool, error) {\n\tvar (\n\t\tignoreError bool\n\t\tcmd         sh.Runner\n\t\terr         error\n\t)\n\n\tcmdName := c.Name()\n\n\tshell.logf(\"Executing: %s\\n\", c.Name())\n\n\tif len(cmdName) > 1 && cmdName[0] == '-' {\n\t\tignoreError = true\n\t\tcmdName = cmdName[1:]\n\n\t\tshell.logf(\"Ignoring error\\n\")\n\t}\n\n\tif cmdName == \"\" {\n\t\treturn nil, false, errors.NewEvalError(shell.filename,\n\t\t\tc, \"Empty command name...\")\n\t}\n\n\tif fnDef, ok := shell.Getbindfn(cmdName); ok {\n\t\trunner, err := shell.newBindfnRunner(c, cmdName, fnDef)\n\t\treturn runner, ignoreError, err\n\t}\n\n\tcmd, err = NewCmd(cmdName)\n\n\tif err != nil {\n\t\ttype NotFound interface {\n\t\t\tNotFound() bool\n\t\t}\n\n\t\tshell.logf(\"Command fails: %s\", err.Error())\n\n\t\tif errNotFound, ok := err.(NotFound); ok && errNotFound.NotFound() {\n\t\t\treturn nil, ignoreError, err\n\t\t}\n\n\t\treturn nil, ignoreError, err\n\t}\n\n\tcmd.SetStdin(shell.stdin)\n\tcmd.SetStdout(shell.stdout)\n\tcmd.SetStderr(shell.stderr)\n\n\treturn cmd, ignoreError, nil\n}\n\nfunc (shell *Shell) executeCommand(c *ast.CommandNode) (sh.Obj, error) {\n\tvar (\n\t\tignoreError    bool\n\t\tstatus         = \"127\"\n\t\tenvVars        []string\n\t\tcloseAfterWait []io.Closer\n\t\tcmd            sh.Runner\n\t\terr            error\n\t\targs           []sh.Obj\n\t)\n\n\tdefer func() {\n\t\tfor _, c := range closeAfterWait {\n\t\t\tc.Close()\n\t\t}\n\t}()\n\n\tcmd, ignoreError, err = shell.getCommand(c)\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\t// SetEnviron must be called before SetArgs\n\t// otherwise the subshell will have the arguments\n\t// shadowed by parent env\n\tenvVars = buildenv(shell.Environ())\n\tcmd.SetEnviron(envVars)\n\n\targs, err = shell.evalExprs(c.Args())\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\terr = cmd.SetArgs(args)\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\tcloseAfterWait, err = shell.setRedirects(cmd, c.Redirects())\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\terr = cmd.Wait()\n\tif err != nil {\n\t\tgoto cmdError\n\t}\n\n\treturn sh.NewStrObj(\"0\"), nil\n\ncmdError:\n\tstatusObj := sh.NewStrObj(getErrStatus(err, status))\n\tif ignoreError {\n\t\treturn statusObj, newErrIgnore(err.Error())\n\t}\n\n\treturn statusObj, err\n}\n\nfunc (shell *Shell) evalList(argList *ast.ListExpr) (sh.Obj, error) {\n\tvalues := make([]sh.Obj, 0, len(argList.List))\n\n\tfor _, arg := range argList.List {\n\t\tobj, err := shell.evalExpr(arg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalues = append(values, obj)\n\t}\n\n\treturn sh.NewListObj(values), nil\n}\n\nfunc (shell *Shell) evalArgList(argList *ast.ListExpr) ([]sh.Obj, error) {\n\tvalues := make([]sh.Obj, 0, len(argList.List))\n\n\tfor _, arg := range argList.List {\n\t\tobj, err := shell.evalExpr(arg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalues = append(values, obj)\n\t}\n\n\tif argList.IsVariadic {\n\t\treturn values, nil\n\t}\n\n\treturn []sh.Obj{sh.NewListObj(values)}, nil\n}\n\nfunc (shell *Shell) evalIndex(index ast.Expr) (int, error) {\n\tif index.Type() != ast.NodeIntExpr && index.Type() != ast.NodeVarExpr && index.Type() != ast.NodeIndexExpr {\n\t\treturn 0, errors.NewEvalError(shell.filename,\n\t\t\tindex, \"Invalid indexing type: %s\", index.Type())\n\t}\n\n\tif index.Type() == ast.NodeIntExpr {\n\t\tidxArg := index.(*ast.IntExpr)\n\t\treturn idxArg.Value(), nil\n\t}\n\n\tidxObj, err := shell.evalVariable(index)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif idxObj.Type() != sh.StringType {\n\t\treturn 0, errors.NewEvalError(shell.filename,\n\t\t\tindex, \"Invalid object type on index value: %s\", idxObj.Type())\n\t}\n\n\tobjstr := idxObj.(*sh.StrObj)\n\tindexNum, err := strconv.Atoi(objstr.Str())\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn indexNum, nil\n}\n\nfunc (shell *Shell) evalIndexedVar(indexVar *ast.IndexExpr) (sh.Obj, error) {\n\tv, err := shell.evalVariable(indexVar.Var)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := sh.NewCollection(v)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())\n\t}\n\n\tindexNum, err := shell.evalIndex(indexVar.Index)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tval, err := col.Get(indexNum)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())\n\t}\n\treturn val, nil\n}\n\nfunc (shell *Shell) evalArgIndexedVar(indexVar *ast.IndexExpr) ([]sh.Obj, error) {\n\tv, err := shell.evalVariable(indexVar.Var)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := sh.NewCollection(v)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())\n\t}\n\n\tindexNum, err := shell.evalIndex(indexVar.Index)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tretval, err := col.Get(indexNum)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())\n\t}\n\n\tif indexVar.IsVariadic {\n\t\tif retval.Type() != sh.ListType {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tindexVar, \"Use of '...' on a non-list variable\")\n\t\t}\n\t\tretlist := retval.(*sh.ListObj)\n\t\treturn retlist.List(), nil\n\t}\n\treturn []sh.Obj{retval}, nil\n}\n\nfunc (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) {\n\tvar (\n\t\tvalue sh.Obj\n\t\tok    bool\n\t)\n\n\tif a.Type() == ast.NodeIndexExpr {\n\t\treturn shell.evalIndexedVar(a.(*ast.IndexExpr))\n\t}\n\n\tif a.Type() != ast.NodeVarExpr {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\ta, \"Invalid eval of non variable argument: %s\", a)\n\t}\n\n\tvexpr := a.(*ast.VarExpr)\n\tvarName := vexpr.Name\n\n\tif value, ok = shell.Getvar(varName[1:]); !ok {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\ta, \"Variable %s not set on shell %s\", varName, shell.name)\n\t}\n\treturn value, nil\n}\n\nfunc (shell *Shell) evalArgVariable(a ast.Expr) ([]sh.Obj, error) {\n\tvar (\n\t\tvalue sh.Obj\n\t\tok    bool\n\t)\n\n\tif a.Type() == ast.NodeIndexExpr {\n\t\treturn shell.evalArgIndexedVar(a.(*ast.IndexExpr))\n\t}\n\n\tif a.Type() != ast.NodeVarExpr {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\ta, \"Invalid eval of non variable argument: %s\", a)\n\t}\n\n\tvexpr := a.(*ast.VarExpr)\n\tif value, ok = shell.Getvar(vexpr.Name[1:]); !ok {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\ta, \"Variable %s not set on shell %s\", vexpr.Name,\n\t\t\tshell.name)\n\t}\n\n\tif vexpr.IsVariadic {\n\t\tif value.Type() != sh.ListType {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\ta, \"Variable expansion (%s) on a non-list object\",\n\t\t\t\tvexpr.String())\n\t\t}\n\n\t\treturn value.(*sh.ListObj).List(), nil\n\t}\n\n\treturn []sh.Obj{value}, nil\n}\n\nfunc (shell *Shell) evalExprs(exprs []ast.Expr) ([]sh.Obj, error) {\n\tobjs := make([]sh.Obj, 0, len(exprs))\n\n\tfor _, expr := range exprs {\n\t\tobj, err := shell.evalExpr(expr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tobjs = append(objs, obj)\n\t}\n\n\treturn objs, nil\n}\n\nfunc (shell *Shell) evalArgExprs(exprs []ast.Expr) ([]sh.Obj, error) {\n\tret := make([]sh.Obj, 0, len(exprs))\n\n\tfor _, expr := range exprs {\n\t\tobjs, err := shell.evalArgExpr(expr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tret = append(ret, objs...)\n\t}\n\n\treturn ret, nil\n}\n\nfunc (shell *Shell) evalArgExpr(expr ast.Expr) ([]sh.Obj, error) {\n\tswitch expr.Type() {\n\tcase ast.NodeStringExpr:\n\t\tif str, ok := expr.(*ast.StringExpr); ok {\n\t\t\treturn []sh.Obj{\n\t\t\t\tsh.NewStrObj(str.Value()),\n\t\t\t}, nil\n\t\t}\n\tcase ast.NodeConcatExpr:\n\t\tif concat, ok := expr.(*ast.ConcatExpr); ok {\n\t\t\targVal, err := shell.evalConcat(concat)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn []sh.Obj{\n\t\t\t\tsh.NewStrObj(argVal),\n\t\t\t}, nil\n\t\t}\n\tcase ast.NodeVarExpr:\n\t\treturn shell.evalArgVariable(expr)\n\tcase ast.NodeIndexExpr:\n\t\tif indexedVar, ok := expr.(*ast.IndexExpr); ok {\n\t\t\treturn shell.evalArgIndexedVar(indexedVar)\n\t\t}\n\tcase ast.NodeListExpr:\n\t\tif listExpr, ok := expr.(*ast.ListExpr); ok {\n\t\t\treturn shell.evalArgList(listExpr)\n\t\t}\n\tcase ast.NodeFnInv:\n\t\tif fnInv, ok := expr.(*ast.FnInvNode); ok {\n\t\t\tobjs, err := shell.executeFnInv(fnInv)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif len(objs) == 0 {\n\t\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\t\texpr,\n\t\t\t\t\t\"Function used in\"+\n\t\t\t\t\t\t\" expression but do not return any value: %s\",\n\t\t\t\t\tfnInv)\n\t\t\t} else if len(objs) != 1 {\n\t\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\t\texpr,\n\t\t\t\t\t\"Function used in\"+\n\t\t\t\t\t\t\" expression but it returns %d values: %10q\",\n\t\t\t\t\tlen(objs), objs)\n\t\t\t}\n\n\t\t\treturn []sh.Obj{objs[0]}, nil\n\t\t}\n\t}\n\n\treturn nil, errors.NewEvalError(shell.filename,\n\t\texpr, \"Failed to eval expression: %+v\", expr)\n}\n\nfunc (shell *Shell) evalExpr(expr ast.Expr) (sh.Obj, error) {\n\tswitch expr.Type() {\n\tcase ast.NodeStringExpr:\n\t\tif str, ok := expr.(*ast.StringExpr); ok {\n\t\t\treturn sh.NewStrObj(str.Value()), nil\n\t\t}\n\tcase ast.NodeConcatExpr:\n\t\tif concat, ok := expr.(*ast.ConcatExpr); ok {\n\t\t\targVal, err := shell.evalConcat(concat)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn sh.NewStrObj(argVal), nil\n\t\t}\n\tcase ast.NodeVarExpr:\n\t\treturn shell.evalVariable(expr)\n\tcase ast.NodeIndexExpr:\n\t\tif indexedVar, ok := expr.(*ast.IndexExpr); ok {\n\t\t\treturn shell.evalIndexedVar(indexedVar)\n\t\t}\n\tcase ast.NodeListExpr:\n\t\tif listExpr, ok := expr.(*ast.ListExpr); ok {\n\t\t\treturn shell.evalList(listExpr)\n\t\t}\n\tcase ast.NodeFnInv:\n\t\tif fnInv, ok := expr.(*ast.FnInvNode); ok {\n\t\t\tobjs, err := shell.executeFnInv(fnInv)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif len(objs) == 0 {\n\t\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\t\texpr,\n\t\t\t\t\t\"Function used in\"+\n\t\t\t\t\t\t\" expression but do not return any value: %s\",\n\t\t\t\t\tfnInv)\n\t\t\t} else if len(objs) != 1 {\n\t\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\t\texpr,\n\t\t\t\t\t\"Function used in\"+\n\t\t\t\t\t\t\" expression but it returns %d values: %10q\",\n\t\t\t\t\tlen(objs), objs)\n\t\t\t}\n\n\t\t\treturn objs[0], nil\n\t\t}\n\t}\n\n\treturn nil, errors.NewEvalError(shell.filename,\n\t\texpr, \"Failed to eval expression: %+v\", expr)\n}\n\nfunc (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error {\n\tfor i := 0; i < len(assign.Names); i++ {\n\t\tname := assign.Names[i]\n\t\tvalue := assign.Values[i]\n\t\terr := shell.initVar(name, value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tobj, ok := shell.GetLocalvar(name.Ident)\n\t\tif !ok {\n\t\t\treturn errors.NewEvalError(shell.filename,\n\t\t\t\tassign,\n\t\t\t\t\"internal error: Setenv not setting local variable '%s'\",\n\t\t\t\tname.Ident,\n\t\t\t)\n\t\t}\n\t\tshell.Setenv(name.Ident, obj)\n\t}\n\treturn nil\n}\n\nfunc (shell *Shell) executeSetenvExec(assign *ast.ExecAssignNode) error {\n\terr := shell.executeExecAssign(assign)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i := 0; i < len(assign.Names); i++ {\n\t\tname := assign.Names[i]\n\t\tobj, ok := shell.GetLocalvar(name.Ident)\n\t\tif !ok {\n\t\t\treturn errors.NewEvalError(shell.filename,\n\t\t\t\tassign,\n\t\t\t\t\"internal error: Setenv not setting local variable '%s'\",\n\t\t\t\tname.Ident,\n\t\t\t)\n\t\t}\n\t\tshell.Setenv(name.Ident, obj)\n\t}\n\treturn nil\n}\n\nfunc (shell *Shell) executeSetenv(v *ast.SetenvNode) error {\n\tvar (\n\t\tvarValue sh.Obj\n\t\tok       bool\n\t\tassign   = v.Assignment()\n\t)\n\n\tif assign != nil {\n\t\tswitch assign.Type() {\n\t\tcase ast.NodeAssign:\n\t\t\treturn shell.executeSetenvAssign(assign.(*ast.AssignNode))\n\t\tcase ast.NodeExecAssign:\n\t\t\treturn shell.executeSetenvExec(assign.(*ast.ExecAssignNode))\n\t\t}\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tv, \"Failed to eval setenv, invalid assignment type: %+v\",\n\t\t\tassign)\n\t}\n\n\tvarValue, ok = shell.Getvar(v.Name)\n\tif !ok {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tv, \"Variable '%s' not set on shell %s\", v.Name,\n\t\t\tshell.name,\n\t\t)\n\t}\n\tshell.Setenv(v.Name, varValue)\n\treturn nil\n}\n\nfunc (shell *Shell) concatElements(expr *ast.ConcatExpr) (string, error) {\n\tvalue := \"\"\n\n\tlist := expr.List()\n\n\tfor i := 0; i < len(list); i++ {\n\t\tec := list[i]\n\n\t\tobj, err := shell.evalExpr(ec)\n\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif obj.Type() != sh.StringType {\n\t\t\treturn \"\", errors.NewEvalError(shell.filename,\n\t\t\t\texpr, \"Impossible to concat elements of type %s\", obj.Type())\n\t\t}\n\n\t\tvalue = value + obj.String()\n\t}\n\n\treturn value, nil\n}\n\nfunc (shell *Shell) execCmdOutput(cmd ast.Node,\n\tgetstderr, ignoreError bool) ([]byte, []byte, sh.Obj, error) {\n\tvar (\n\t\toutBuf, errBuf bytes.Buffer\n\t\terr            error\n\t\tstatus         sh.Obj\n\t)\n\tif cmd.Type() != ast.NodeCommand &&\n\t\tcmd.Type() != ast.NodePipe {\n\t\treturn nil, nil, nil, errors.NewEvalError(shell.filename,\n\t\t\tcmd, \"Invalid node type (%v). Expected command or pipe\",\n\t\t\tcmd)\n\t}\n\n\tbkStdout, bkStderr := shell.stdout, shell.stderr\n\tshell.SetStdout(&outBuf)\n\tif getstderr {\n\t\tshell.SetStderr(&errBuf)\n\t}\n\tdefer func() {\n\t\tshell.SetStdout(bkStdout)\n\t\tshell.SetStderr(bkStderr)\n\t}()\n\n\tif cmd.Type() == ast.NodeCommand {\n\t\tstatus, err = shell.executeCommand(cmd.(*ast.CommandNode))\n\t} else {\n\t\tstatus, err = shell.executePipe(cmd.(*ast.PipeNode))\n\t}\n\n\toutb := outBuf.Bytes()\n\terrb := errBuf.Bytes()\n\n\ttrimnl := func(data []byte) []byte {\n\t\tif len(data) > 0 && data[len(data)-1] == '\\n' {\n\t\t\t// remove the trailing new line\n\t\t\t// Why? because it's what user wants in 99.99% of times...\n\n\t\t\tdata = data[0 : len(data)-1]\n\t\t}\n\t\treturn data[:]\n\t}\n\n\tif ignoreError {\n\t\terr = nil\n\t}\n\n\treturn trimnl(outb), trimnl(errb), status, err\n}\n\nfunc (shell *Shell) executeExecAssignCmd(v ast.Node) (stdout, stderr, status sh.Obj, err error) {\n\tassign := v.(*ast.ExecAssignNode)\n\tcmd := assign.Command()\n\n\tmustIgnoreErr := len(assign.Names) > 1\n\tcollectStderr := len(assign.Names) == 3\n\n\toutb, errb, status, err := shell.execCmdOutput(cmd, collectStderr, mustIgnoreErr)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\treturn sh.NewStrObj(string(outb)), sh.NewStrObj(string(errb)), status, nil\n}\n\nfunc (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode) ([]sh.Obj, error) {\n\tvar (\n\t\terr      error\n\t\tfnValues []sh.Obj\n\t)\n\n\tcmd := assign.Command()\n\tif cmd.Type() != ast.NodeFnInv {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tcmd, \"Invalid node type (%v). Expected function call\",\n\t\t\tcmd)\n\t}\n\n\tfnValues, err = shell.executeFnInv(cmd.(*ast.FnInvNode))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(fnValues) != len(assign.Names) {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tassign, \"Functions returns %d objects, but statement expects %d\",\n\t\t\tlen(fnValues), len(assign.Names))\n\t}\n\n\treturn fnValues, nil\n}\n\nfunc (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) (err error) {\n\texec := v.Command()\n\tswitch exec.Type() {\n\tcase ast.NodeFnInv:\n\t\tvar values []sh.Obj\n\t\tvalues, err = shell.executeExecAssignFn(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = shell.setvars(v.Names, values)\n\tcase ast.NodeCommand, ast.NodePipe:\n\t\tvar stdout, stderr, status sh.Obj\n\t\tstdout, stderr, status, err = shell.executeExecAssignCmd(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = shell.setcmdvars(v.Names, stdout, stderr, status)\n\tdefault:\n\t\terr = errors.NewEvalError(shell.filename,\n\t\t\texec, \"Invalid node type (%v). Expected function call, command or pipe\",\n\t\t\texec)\n\t}\n\n\treturn err\n}\n\nfunc (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error {\n\tobj, err := shell.evalExpr(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn shell.newvar(name, obj)\n}\n\nfunc (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error {\n\tassign := v.Assign\n\tif len(assign.Names) != len(assign.Values) {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tassign, \"Invalid multiple assignment. Different amount of variables and values: %s\",\n\t\t\tassign,\n\t\t)\n\t}\n\n\tfor i := 0; i < len(assign.Names); i++ {\n\t\tname := assign.Names[i]\n\t\tvalue := assign.Values[i]\n\n\t\terr := shell.initVar(name, value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) (err error) {\n\tassign := v.ExecAssign\n\texec := assign.Command()\n\tswitch exec.Type() {\n\tcase ast.NodeFnInv:\n\t\tvar values []sh.Obj\n\t\tvalues, err = shell.executeExecAssignFn(assign)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tshell.newvars(assign.Names, values)\n\tcase ast.NodeCommand, ast.NodePipe:\n\t\tvar stdout, stderr, status sh.Obj\n\t\tstdout, stderr, status, err = shell.executeExecAssignCmd(assign)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tshell.newcmdvars(assign.Names, stdout, stderr, status)\n\tdefault:\n\t\terr = errors.NewEvalError(shell.filename,\n\t\t\texec, \"Invalid node type (%v). Expected function call, command or pipe\",\n\t\t\texec)\n\t}\n\n\treturn err\n}\n\nfunc (shell *Shell) executeAssignment(v *ast.AssignNode) error {\n\tif len(v.Names) != len(v.Values) {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tv, \"Invalid multiple assignment. Different amount of variables and values: %s\",\n\t\t\tv,\n\t\t)\n\t}\n\n\tfor i := 0; i < len(v.Names); i++ {\n\t\tname := v.Names[i]\n\t\tvalue := v.Values[i]\n\n\t\tobj, err := shell.evalExpr(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = shell.setvar(name, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (shell *Shell) evalIfArgument(arg ast.Node) (sh.Obj, error) {\n\tvar (\n\t\tobj sh.Obj\n\t\terr error\n\t)\n\n\tobj, err = shell.evalExpr(arg)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if obj == nil {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\targ, \"lvalue doesn't yield value (%s)\", arg)\n\t}\n\n\treturn obj, nil\n}\n\nfunc (shell *Shell) evalIfArguments(n *ast.IfNode) (string, string, error) {\n\tvar (\n\t\tlobj, robj sh.Obj\n\t\terr        error\n\t)\n\n\tlobj, err = shell.evalIfArgument(n.Lvalue())\n\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\trobj, err = shell.evalIfArgument(n.Rvalue())\n\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tif lobj.Type() != sh.StringType {\n\t\treturn \"\", \"\", errors.NewEvalError(shell.filename,\n\t\t\tn, \"lvalue is not comparable: (%v) -> %s.\", lobj, lobj.Type())\n\t}\n\n\tif robj.Type() != sh.StringType {\n\t\treturn \"\", \"\", errors.NewEvalError(shell.filename,\n\t\t\tn, \"rvalue is not comparable: (%v) -> %s.\", lobj, lobj.Type())\n\t}\n\n\tlobjstr := lobj.(*sh.StrObj)\n\trobjstr := robj.(*sh.StrObj)\n\n\treturn lobjstr.Str(), robjstr.Str(), nil\n}\n\nfunc (shell *Shell) executeIfEqual(n *ast.IfNode) ([]sh.Obj, error) {\n\tlstr, rstr, err := shell.evalIfArguments(n)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif lstr == rstr {\n\t\treturn shell.executeTree(n.IfTree(), false)\n\t} else if n.ElseTree() != nil {\n\t\treturn shell.executeTree(n.ElseTree(), false)\n\t}\n\n\treturn nil, nil\n}\n\nfunc (shell *Shell) executeIfNotEqual(n *ast.IfNode) ([]sh.Obj, error) {\n\tlstr, rstr, err := shell.evalIfArguments(n)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif lstr != rstr {\n\t\treturn shell.executeTree(n.IfTree(), false)\n\t} else if n.ElseTree() != nil {\n\t\treturn shell.executeTree(n.ElseTree(), false)\n\t}\n\n\treturn nil, nil\n}\n\nfunc (shell *Shell) executeFnInv(n *ast.FnInvNode) ([]sh.Obj, error) {\n\tvar fnDef sh.FnDef\n\n\tfnName := n.Name()\n\tif len(fnName) > 1 && fnName[0] == '$' {\n\t\targVar := ast.NewVarExpr(token.NewFileInfo(n.Line(), n.Column()), fnName)\n\n\t\tobj, err := shell.evalVariable(argVar)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif obj.Type() != sh.FnType {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tn, \"Variable '%s' is not a function.\", fnName)\n\t\t}\n\n\t\tobjfn := obj.(*sh.FnObj)\n\t\tfnDef = objfn.Fn()\n\t} else {\n\t\tfnObj, err := shell.GetFn(fnName)\n\t\tif err != nil {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tn, err.Error())\n\t\t}\n\t\tfnDef = fnObj.Fn()\n\t}\n\n\tfn := fnDef.Build()\n\targs, err := shell.evalArgExprs(n.Args())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = fn.SetArgs(args)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tn, err.Error())\n\t}\n\n\tfn.SetStdin(shell.stdin)\n\tfn.SetStdout(shell.stdout)\n\tfn.SetStderr(shell.stderr)\n\n\terr = fn.Start()\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tn, err.Error())\n\t}\n\n\terr = fn.Wait()\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tn, err.Error())\n\t}\n\n\treturn fn.Results(), nil\n}\n\nfunc (shell *Shell) executeInfLoop(tr *ast.Tree) ([]sh.Obj, error) {\n\tvar (\n\t\terr  error\n\t\tobjs []sh.Obj\n\t)\n\n\tfor {\n\t\tobjs, err = shell.executeTree(tr, false)\n\n\t\truntime.Gosched()\n\n\t\ttype (\n\t\t\tinterruptedError interface {\n\t\t\t\tInterrupted() bool\n\t\t\t}\n\n\t\t\tstopWalkingError interface {\n\t\t\t\tStopWalking() bool\n\t\t\t}\n\t\t)\n\n\t\tif errInterrupted, ok := err.(interruptedError); ok && errInterrupted.Interrupted() {\n\t\t\tbreak\n\t\t}\n\n\t\tif errStopWalking, ok := err.(stopWalkingError); ok && errStopWalking.StopWalking() {\n\t\t\treturn objs, err\n\t\t}\n\n\t\tshell.Lock()\n\n\t\tif shell.getIntr() {\n\t\t\tshell.setIntr(false)\n\n\t\t\tif err != nil {\n\t\t\t\terr = newErrInterrupted(err.Error())\n\t\t\t} else {\n\t\t\t\terr = newErrInterrupted(\"loop interrupted\")\n\t\t\t}\n\t\t}\n\n\t\tshell.Unlock()\n\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil, err\n}\n\nfunc (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) {\n\tshell.Lock()\n\tshell.looping = true\n\tshell.Unlock()\n\n\tdefer func() {\n\t\tshell.Lock()\n\t\tdefer shell.Unlock()\n\n\t\tshell.looping = false\n\t}()\n\n\tif n.InExpr() == nil {\n\t\treturn shell.executeInfLoop(n.Tree())\n\t}\n\n\tid := n.Identifier()\n\tinExpr := n.InExpr()\n\n\tvar (\n\t\tobj sh.Obj\n\t\terr error\n\t)\n\n\tif inExpr.Type() == ast.NodeVarExpr {\n\t\tobj, err = shell.evalVariable(inExpr.(*ast.VarExpr))\n\t} else if inExpr.Type() == ast.NodeListExpr {\n\t\tobj, err = shell.evalList(inExpr.(*ast.ListExpr))\n\t} else if inExpr.Type() == ast.NodeFnInv {\n\t\tvar objs []sh.Obj\n\t\tobjs, err = shell.executeFnInv(inExpr.(*ast.FnInvNode))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif len(objs) != 1 {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tinExpr, \"Functions with multiple returns do not work as for 'in expression' yet: %v\", inExpr)\n\t\t}\n\n\t\tobj = objs[0]\n\t} else {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tinExpr, \"Invalid expression in for loop: %s\", inExpr.Type())\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcol, err := sh.NewCollection(obj)\n\tif err != nil {\n\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\tinExpr, \"error[%s] trying to iterate\", err)\n\t}\n\n\tfor i := 0; i < col.Len(); i++ {\n\t\tval, err := col.Get(i)\n\t\tif err != nil {\n\t\t\treturn nil, errors.NewEvalError(shell.filename,\n\t\t\t\tinExpr, \"unexpected error[%s] during iteration\", err)\n\t\t}\n\t\tshell.Newvar(id, val)\n\t\tobjs, err := shell.executeTree(n.Tree(), false)\n\n\t\ttype (\n\t\t\tinterruptedError interface {\n\t\t\t\tInterrupted() bool\n\t\t\t}\n\n\t\t\tstopWalkingError interface {\n\t\t\t\tStopWalking() bool\n\t\t\t}\n\t\t)\n\n\t\tif errInterrupted, ok := err.(interruptedError); ok && errInterrupted.Interrupted() {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif errStopWalking, ok := err.(stopWalkingError); ok && errStopWalking.StopWalking() {\n\t\t\treturn objs, err\n\t\t}\n\n\t\tshell.Lock()\n\n\t\tif shell.getIntr() {\n\t\t\tshell.setIntr(false)\n\t\t\tshell.Unlock()\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, newErrInterrupted(err.Error())\n\t\t\t}\n\n\t\t\treturn nil, newErrInterrupted(\"loop interrupted\")\n\t\t}\n\n\t\tshell.Unlock()\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n\nfunc (shell *Shell) executeFnDecl(n *ast.FnDeclNode) error {\n\tfnDef, err := newUserFnDef(n.Name(), shell, n.Args(), n.Tree())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tshell.Newvar(n.Name(), sh.NewFnObj(fnDef))\n\tshell.logf(\"Function %s declared on '%s'\", n.Name(), shell.name)\n\treturn nil\n}\n\nfunc (shell *Shell) executeBindFn(n *ast.BindFnNode) error {\n\tif !shell.Interactive() {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tn, \"'bindfn' is not allowed in non-interactive mode.\")\n\t}\n\n\tfnDef, err := shell.GetFn(n.Name())\n\tif err != nil {\n\t\treturn errors.NewEvalError(shell.filename,\n\t\t\tn, err.Error())\n\t}\n\n\tshell.Setbindfn(n.CmdName(), fnDef.Fn())\n\treturn nil\n}\n\nfunc (shell *Shell) executeIf(n *ast.IfNode) ([]sh.Obj, error) {\n\top := n.Op()\n\n\tif op == \"==\" {\n\t\treturn shell.executeIfEqual(n)\n\t} else if op == \"!=\" {\n\t\treturn shell.executeIfNotEqual(n)\n\t}\n\n\treturn nil, fmt.Errorf(\"invalid operation '%s'\", op)\n}\n\nfunc validateDirs(nashpath string, nashroot string) error {\n\tif nashpath == nashroot {\n\t\treturn fmt.Errorf(\"invalid nashpath and nashroot, they are both[%s] but they must differ\", nashpath)\n\t}\n\terr := validateDir(nashpath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid nashpath, user's config won't be loaded: error: %s\", err)\n\t}\n\terr = validateDir(nashroot)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid nashroot, stdlib/stdbin won't be available: error: %s\", err)\n\t}\n\treturn nil\n}\n\nfunc validateDir(dir string) error {\n\tdir, err := filepath.EvalSymlinks(dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinfo, err := os.Stat(dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !info.IsDir() {\n\t\treturn fmt.Errorf(\"%s is a file, expected a dir\", dir)\n\t}\n\tif !filepath.IsAbs(dir) {\n\t\treturn fmt.Errorf(\"%s is a relative path, expected a absolute path\", dir)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/sh/shell_import_test.go",
    "content": "package sh_test\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/internal/sh\"\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n)\n\nfunc TestImportsLibFromNashPathLibDir(t *testing.T) {\n\n\tnashdirs := fixture.SetupNashDirs(t)\n\tdefer nashdirs.Cleanup()\n\n\twriteFile(t, filepath.Join(nashdirs.Lib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"hasnashpath\"\n\t\t}\n\t`)\n\n\tnewTestShell(t, nashdirs.Path, nashdirs.Root).ExecCheckingOutput(t, `\n\t\timport lib\n\t\ttest()\n\t`, \"hasnashpath\\n\")\n}\n\nfunc TestImportsLibFromNashPathLibDirBeforeNashRootStdlib(t *testing.T) {\n\n\tnashdirs := fixture.SetupNashDirs(t)\n\tdefer nashdirs.Cleanup()\n\n\twriteFile(t, filepath.Join(nashdirs.Lib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"libcode\"\n\t\t}\n\t`)\n\n\twriteFile(t, filepath.Join(nashdirs.Stdlib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"stdlibcode\"\n\t\t}\n\t`)\n\n\tnewTestShell(t, nashdirs.Path, nashdirs.Root).ExecCheckingOutput(t, `\n\t\timport lib\n\t\ttest()\n\t`, \"libcode\\n\")\n}\n\nfunc TestImportsLibFromNashRootStdlib(t *testing.T) {\n\n\tnashdirs := fixture.SetupNashDirs(t)\n\tdefer nashdirs.Cleanup()\n\n\twriteFile(t, filepath.Join(nashdirs.Stdlib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"stdlibcode\"\n\t\t}\n\t`)\n\n\tnewTestShell(t, nashdirs.Path, nashdirs.Root).ExecCheckingOutput(t, `\n\t\timport lib\n\t\ttest()\n\t`, \"stdlibcode\\n\")\n}\n\nfunc TestImportsLibFromWorkingDirBeforeLibAndStdlib(t *testing.T) {\n\n\tworkingdir, rmdir := fixture.Tmpdir(t)\n\tdefer rmdir()\n\n\tcurwd := getwd(t)\n\tchdir(t, workingdir)\n\tdefer chdir(t, curwd)\n\n\tnashdirs := fixture.SetupNashDirs(t)\n\tdefer nashdirs.Cleanup()\n\n\twriteFile(t, filepath.Join(workingdir, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"localcode\"\n\t\t}\n\t`)\n\n\twriteFile(t, filepath.Join(nashdirs.Lib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"libcode\"\n\t\t}\n\t`)\n\n\twriteFile(t, filepath.Join(nashdirs.Stdlib, \"lib.sh\"), `\n\t\tfn test() {\n\t\t\techo \"stdlibcode\"\n\t\t}\n\t`)\n\n\tnewTestShell(t, nashdirs.Path, nashdirs.Root).ExecCheckingOutput(t, `\n\t\timport lib\n\t\ttest()\n\t`, \"localcode\\n\")\n}\n\nfunc TestStdErrOnInvalidSearchPaths(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tnashpath string\n\t\tnashroot string\n\t\terrmsg   string\n\t}\n\n\tconst nashrooterr = \"invalid nashroot\"\n\tconst nashpatherr = \"invalid nashpath\"\n\n\tvalidDir, rmdir := fixture.Tmpdir(t)\n\tdefer rmdir()\n\n\tvalidfile := filepath.Join(validDir, \"notdir\")\n\twriteFile(t, validfile, \"whatever\")\n\n\tcases := []testCase{\n\t\t{\n\t\t\tname:     \"EmptyNashPath\",\n\t\t\tnashpath: \"\",\n\t\t\tnashroot: validDir,\n\t\t\terrmsg:   nashpatherr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashPathDontExists\",\n\t\t\tnashpath: filepath.Join(validDir, \"dontexists\"),\n\t\t\tnashroot: validDir,\n\t\t\terrmsg:   nashpatherr,\n\t\t},\n\t\t{\n\t\t\tname:     \"EmptyNashRoot\",\n\t\t\tnashpath: validDir,\n\t\t\tnashroot: \"\",\n\t\t\terrmsg:   nashrooterr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashRootDontExists\",\n\t\t\tnashroot: filepath.Join(validDir, \"dontexists\"),\n\t\t\tnashpath: validDir,\n\t\t\terrmsg:   nashrooterr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashPathIsFile\",\n\t\t\tnashroot: validDir,\n\t\t\tnashpath: validfile,\n\t\t\terrmsg:   nashpatherr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashRootIsFile\",\n\t\t\tnashroot: validfile,\n\t\t\tnashpath: validDir,\n\t\t\terrmsg:   nashrooterr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashPathIsRelative\",\n\t\t\tnashroot: validDir,\n\t\t\tnashpath: \"./\",\n\t\t\terrmsg:   nashpatherr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashRootIsRelative\",\n\t\t\tnashroot: \"./\",\n\t\t\tnashpath: validDir,\n\t\t\terrmsg:   nashrooterr,\n\t\t},\n\t\t{\n\t\t\tname:     \"NashRootAndNashPathAreEqual\",\n\t\t\tnashroot: validDir,\n\t\t\tnashpath: validDir,\n\t\t\terrmsg:   \"invalid nashpath and nashroot\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\t_, err := sh.NewAbortShell(c.nashpath, c.nashroot)\n\t\t\tif c.errmsg != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"expected err[%s]\", c.errmsg)\n\t\t\t\t}\n\t\t\t\tif !strings.HasPrefix(err.Error(), c.errmsg) {\n\t\t\t\t\tt.Fatalf(\"errors mismatch: [%s] didnt contains [%s]\", err, c.errmsg)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"got unexpected error[%s]\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype testshell struct {\n\tshell  *sh.Shell\n\tstdout *bytes.Buffer\n}\n\nfunc (s *testshell) ExecCheckingOutput(t *testing.T, code string, expectedOutupt string) {\n\terr := s.shell.Exec(\"shellenvtest\", code)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toutput := s.stdout.String()\n\ts.stdout.Reset()\n\n\tif output != expectedOutupt {\n\t\tt.Fatalf(\n\t\t\t\"expected output: [%s] got: [%s]\",\n\t\t\texpectedOutupt,\n\t\t\toutput,\n\t\t)\n\t}\n}\n\nfunc newTestShell(t *testing.T, nashpath string, nashroot string) *testshell {\n\n\tshell, err := sh.NewShell(nashpath, nashroot)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar out bytes.Buffer\n\tshell.SetStdout(&out)\n\n\treturn &testshell{shell: shell, stdout: &out}\n}\n"
  },
  {
    "path": "internal/sh/shell_linux_test.go",
    "content": "// +build linux\n\npackage sh_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n)\n\nvar (\n\tenableUserNS bool\n)\n\nfunc init() {\n\tconst usernsOk = \"1\"\n\tconst kernelcfg = \"CONFIG_USER_NS\"\n\n\tlogUsernsDetection := func(err error) {\n\t\tif enableUserNS {\n\t\t\tfmt.Printf(\"Linux user namespaces enabled!\")\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\"Warning: Impossible to know if kernel support USER namespace.\\n\")\n\t\tfmt.Printf(\"Warning: USER namespace tests will not run.\\n\")\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"ERROR: %s\\n\", err)\n\t\t}\n\t}\n\n\tusernsCfg := \"/proc/sys/kernel/unprivileged_userns_clone\"\n\tval, permerr := ioutil.ReadFile(usernsCfg)\n\n\t// Travis build doesn't support /proc/config.gz but kernel has userns\n\tif os.Getenv(\"TRAVIS_BUILD\") == \"1\" {\n\t\tenableUserNS = permerr == nil && string(val) == usernsOk\n\t\tlogUsernsDetection(permerr)\n\t\treturn\n\t}\n\n\tif permerr == nil {\n\t\tenableUserNS = string(val) == usernsOk\n\t\tlogUsernsDetection(permerr)\n\t\treturn\n\t}\n\n\t// old kernels dont have sysctl configurations\n\t// than just checking the /proc/config suffices\n\tusernsCmd := exec.Command(\"zgrep\", kernelcfg, \"/proc/config.gz\")\n\n\tcontent, err := usernsCmd.CombinedOutput()\n\tif err != nil {\n\t\tenableUserNS = false\n\t\tlogUsernsDetection(fmt.Errorf(\"Failed to get kernel config: %s\", err))\n\t\treturn\n\t}\n\n\tcfgVal := strings.Trim(string(content), \"\\n\\t \")\n\tenableUserNS = cfgVal == kernelcfg+\"=y\"\n\tlogUsernsDetection(fmt.Errorf(\"%s not enabled in kernel config\", kernelcfg))\n}\n\nfunc TestExecuteRforkUserNS(t *testing.T) {\n\tif !enableUserNS {\n\t\tt.Skip(\"User namespace not enabled\")\n\t\treturn\n\t}\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\terr := f.shell.Exec(\"rfork test\", `\n        rfork u {\n            id -u\n        }\n        `)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(f.shellOut.Bytes()) != \"0\\n\" {\n\t\tt.Errorf(\"User namespace not supported in your kernel: %s\", string(f.shellOut.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteRforkEnvVars(t *testing.T) {\n\tif !enableUserNS {\n\t\tt.Skip(\"User namespace not enabled\")\n\t\treturn\n\t}\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tsh := f.shell\n\n\terr := sh.Exec(\"test env\", `var abra = \"cadabra\"\nsetenv abra\nrfork up {\n\techo $abra\n}`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n}\n\nfunc TestExecuteRforkUserNSNested(t *testing.T) {\n\tif !enableUserNS {\n\t\tt.Skip(\"User namespace not enabled\")\n\t\treturn\n\t}\n\n\tvar out bytes.Buffer\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tsh := f.shell\n\n\tsh.SetStdout(&out)\n\n\terr := sh.Exec(\"rfork userns nested\", `\n        rfork u {\n            id -u\n            rfork u {\n                id -u\n            }\n        }\n        `)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"0\\n0\\n\" {\n\t\tt.Errorf(\"User namespace not supported in your kernel\")\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "internal/sh/shell_regression_test.go",
    "content": "package sh_test\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"path\"\n\t\"fmt\"\n)\n\nfunc TestExecuteIssue68(t *testing.T) {\n\tf, cleanup := setup(t)\n\tdefer cleanup()\n\t\n\tsh := f.shell\n\n\ttmpDir, err := ioutil.TempDir(\"\", \"nash-tests\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfile := path.Join(tmpDir, \"la\")\n\terr = sh.Exec(\"-input-\", fmt.Sprintf(`echo lalalala | grep la > %s`, file))\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tdefer os.Remove(file)\n\n\tcontents, err := ioutil.ReadFile(file)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontentStr := strings.TrimSpace(string(contents))\n\tif contentStr != \"lalalala\" {\n\t\tt.Errorf(\"Strings differ: '%s' != '%s'\", contentStr, \"lalalala\")\n\t\treturn\n\t}\n}\n\nfunc TestExecuteErrorSuppression(t *testing.T) {\n\tf, cleanup := setup(t)\n\tdefer cleanup()\n\n\tsh := f.shell\n\terr := sh.Exec(\"-input-\", `-bllsdlfjlsd`)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected to not fail...: %s\", err.Error())\n\t\treturn\n\t}\n\n\t// issue #72\n\terr = sh.Exec(\"-input-\", `echo lalala | -grep lelele`)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected to not fail...:(%s)\", err.Error())\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "internal/sh/shell_test.go",
    "content": "package sh_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t// FIXME: depending on other sh package on the internal sh tests seems very odd\n\tshtypes \"github.com/madlambda/nash/sh\"\n\n\t\"github.com/madlambda/nash/internal/sh\"\n\t\"github.com/madlambda/nash/internal/sh/internal/fixture\"\n\t\"github.com/madlambda/nash/tests\"\n)\n\ntype (\n\texecTestCase struct {\n\t\tdesc              string\n\t\tcode              string\n\t\texpectedStdout    string\n\t\texpectedStderr    string\n\t\texpectedErr       string\n\t\texpectedPrefixErr string\n\t}\n\n\ttestFixture struct {\n\t\tshell     *sh.Shell\n\t\tshellOut  *bytes.Buffer\n\t\tdir       string\n\t\tenvDirs   fixture.NashDirs\n\t\tnashdPath string\n\t}\n)\n\nfunc TestInitEnv(t *testing.T) {\n\tos.Setenv(\"TEST\", \"abc=123=\")\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\ttestEnv, ok := f.shell.Getenv(\"TEST\")\n\tif !ok {\n\t\tt.Fatal(\"environment TEST not found\")\n\t}\n\texpectedTestEnv := \"abc=123=\"\n\n\tif testEnv.String() != expectedTestEnv {\n\t\tt.Fatalf(\"Expected TEST Env differs: '%s' != '%s'\", testEnv, expectedTestEnv)\n\t}\n}\n\nfunc TestExecuteFile(t *testing.T) {\n\ttype fileTests struct {\n\t\tpath       string\n\t\texpected   string\n\t\texecBefore string\n\t}\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tfor _, ftest := range []fileTests{\n\t\t{path: \"/ex1.sh\", expected: \"hello world\\n\"},\n\n\t\t{path: \"/sieve.sh\", expected: \"\\n\", execBefore: `var ARGS = (\"\" \"0\")`},\n\t\t{path: \"/sieve.sh\", expected: \"\\n\", execBefore: `var ARGS = (\"\" \"1\")`},\n\t\t{path: \"/sieve.sh\", expected: \"2 \\n\", execBefore: `var ARGS = (\"\" \"2\")`},\n\t\t{path: \"/sieve.sh\", expected: \"2 3 \\n\", execBefore: `var ARGS = (\"\" \"3\")`},\n\t\t{path: \"/sieve.sh\", expected: \"2 3 \\n\", execBefore: `var ARGS = (\"\" \"4\")`},\n\t\t{path: \"/sieve.sh\", expected: \"2 3 5 \\n\", execBefore: `var ARGS = (\"\" \"5\")`},\n\t\t{path: \"/sieve.sh\", expected: \"2 3 5 7 \\n\", execBefore: `var ARGS = (\"\" \"10\")`},\n\n\t\t{path: \"/fibonacci.sh\", expected: \"1 \\n\", execBefore: `var ARGS = (\"\" \"1\")`},\n\t\t{path: \"/fibonacci.sh\", expected: \"1 2 \\n\", execBefore: `var ARGS = (\"\" \"2\")`},\n\t\t{path: \"/fibonacci.sh\", expected: \"1 2 3 \\n\", execBefore: `var ARGS = (\"\" \"3\")`},\n\t\t{path: \"/fibonacci.sh\", expected: \"1 2 3 5 8 \\n\", execBefore: `var ARGS = (\"\" \"5\")`},\n\t} {\n\t\ttestExecuteFile(t, f.dir+ftest.path, ftest.expected, ftest.execBefore)\n\t}\n}\n\nfunc TestExecuteCommand(t *testing.T) {\n\techopath, err := exec.LookPath(\"echo\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\techodir := filepath.Dir(echopath)\n\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc:              \"command failed\",\n\t\t\tcode:              `non-existing-program`,\n\t\t\texpectedStdout:    \"\",\n\t\t\texpectedStderr:    \"\",\n\t\t\texpectedPrefixErr: `exec: \"non-existing-program\": executable file not found in `,\n\t\t},\n\t\t{\n\t\t\tdesc:           \"err ignored\",\n\t\t\tcode:           `-non-existing-program`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"hello world\",\n\t\t\tcode:           \"echo -n hello world\",\n\t\t\texpectedStdout: \"hello world\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"cmd with concat\",\n\t\t\tcode:           `echo -n \"hello \" + \"world\"`,\n\t\t\texpectedStdout: \"hello world\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"local command\",\n\t\t\tcode: fmt.Sprintf(`var echodir = \"%s\"\nchdir($echodir)\n./echo -n hello\n`, strings.Replace(echodir, \"\\\\\", \"\\\\\\\\\", -1)),\n\t\t\texpectedStdout: \"hello\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteAssignment(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{ // wrong assignment\n\t\t\tdesc:           \"wrong assignment\",\n\t\t\tcode:           `var name=i4k`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"assignment\",\n\t\t\tcode: `var name=\"i4k\"\n                         echo $name`,\n\t\t\texpectedStdout: \"i4k\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var name=(honda civic)\n                         echo -n $name`,\n\t\t\texpectedStdout: \"honda civic\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list of lists\",\n\t\t\tcode: `var l = (\n\t\t(name Archlinux)\n\t\t(arch amd64)\n\t\t(kernel 4.7.1)\n\t)\n\n\techo $l[0]\n\techo $l[1]\n\techo -n $l[2]`,\n\t\t\texpectedStdout: `name Archlinux\narch amd64\nkernel 4.7.1`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         l[0] = \"666\"\n                         echo -n $l`,\n\t\t\texpectedStdout: `666 1 2 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         var a = \"2\"\n                         l[$a] = \"666\"\n                         echo -n $l`,\n\t\t\texpectedStdout: `0 1 666 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteMultipleAssignment(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2 = \"1\", \"2\"\n\t\t\t\techo -n $_1 $_2`,\n\t\t\texpectedStdout: \"1 2\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2, _3 = \"1\", \"2\", \"3\"\n\t\t\t\techo -n $_1 $_2 $_3`,\n\t\t\texpectedStdout: \"1 2 3\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2 = (), ()\n\t\t\t\techo -n $_1 $_2`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2 = (1 2 3 4 5), (6 7 8 9 10)\n\t\t\t\techo -n $_1 $_2`,\n\t\t\texpectedStdout: \"1 2 3 4 5 6 7 8 9 10\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2, _3, _4, _5, _6, _7, _8, _9, _10 = \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"\n\t\t\t\techo -n $_1 $_2 $_3 $_4 $_5 $_6 $_7 $_8 $_9 $_10`,\n\t\t\texpectedStdout: \"1 2 3 4 5 6 7 8 9 10\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `var _1, _2 = (a b c), \"d\"\n\t\t\t\techo -n $_1 $_2`,\n\t\t\texpectedStdout: \"a b c d\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple assignment\",\n\t\t\tcode: `fn a() { echo -n \"a\" }\n\t\t\t\t  fn b() { echo -n \"b\" }\n\t\t\t\t  var _a, _b = $a, $b\n\t\t\t\t  $_a(); $_b()`,\n\t\t\texpectedStdout: \"ab\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteCmdAssignment(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"cmd assignment\",\n\t\t\tcode: `var name <= echo -n i4k\n                         echo -n $name`,\n\t\t\texpectedStdout: \"i4k\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list cmd assignment\",\n\t\t\tcode: `var name <= echo \"honda civic\"\n                         echo -n $name`,\n\t\t\texpectedStdout: \"honda civic\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"wrong cmd assignment\",\n\t\t\tcode:           `var name <= \"\"`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"wrong cmd assignment:1:13: Invalid token STRING. Expected command or function invocation\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"fn must return value\",\n\t\t\tcode: `fn e() {}\n                         var v <= e()`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"<interactive>:2:29: Functions returns 0 objects, but statement expects 1\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         l[0] <= echo -n 666\n                         echo -n $l`,\n\t\t\texpectedStdout: `666 1 2 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         var a = \"2\"\n                         l[$a] <= echo -n \"666\"\n                         echo -n $l`,\n\t\t\texpectedStdout: `0 1 666 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteCmdMultipleAssignment(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"cmd assignment\",\n\t\t\tcode: `var name, err <= echo -n i4k\n                         if $err == \"0\" {\n                             echo -n $name\n                         }`,\n\t\t\texpectedStdout: \"i4k\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list cmd assignment\",\n\t\t\tcode: `var name, err2 <= echo \"honda civic\"\n                         if $err2 == \"0\" {\n                             echo -n $name\n                         }`,\n\t\t\texpectedStdout: \"honda civic\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"wrong cmd assignment\",\n\t\t\tcode:           `var name, err <= \"\"`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"wrong cmd assignment:1:18: Invalid token STRING. Expected command or function invocation\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"fn must return value\",\n\t\t\tcode: `fn e() {}\n                         var v, err <= e()`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"<interactive>:2:29: Functions returns 0 objects, but statement expects 2\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         var l[0], err <= echo -n 666\n                         if $err == \"0\" {\n                             echo -n $l\n                         }`,\n\t\t\texpectedStdout: `666 1 2 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"list assignment\",\n\t\t\tcode: `var l = (0 1 2 3)\n                         var a = \"2\"\n                         var l[$a], err <= echo -n \"666\"\n                         if $err == \"0\" {\n                             echo -n $l\n                         }`,\n\t\t\texpectedStdout: `0 1 666 3`,\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"cmd assignment works with 1 or 2 variables\",\n\t\t\tcode:           \"var out, err, status <= echo something\",\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"ignore error\",\n\t\t\tcode: `var out, _ <= cat /file-not-found/test >[2=]\n\t\t\t\t\techo -n $out`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"exec without '-' and getting status still fails\",\n\t\t\tcode: `var out <= cat /file-not-found/test >[2=]\n\t\t\t\t\techo $out`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"exit status 1\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"check status\",\n\t\t\tcode: `var out, status <= cat /file-not-found/test >[2=]\n\t\t\t\t\tif $status == \"0\" {\n\t\t\t\t\t\techo -n \"must fail.. sniff\"\n\t\t\t\t\t} else if $status == \"1\" {\n\t\t\t\t\t\techo -n \"it works\"\n\t\t\t\t\t} else {\n\t\t\t\t\t\techo -n \"unexpected status:\" $status\n\t\t\t\t\t}\n\t\t\t\t`,\n\t\t\texpectedStdout: \"it works\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple return in functions\",\n\t\t\tcode: `fn fun() {\n\t\t\t\t\treturn \"1\", \"2\"\n\t\t\t\t}\n\n\t\t\t\tvar a, b <= fun()\n\t\t\t\techo -n $a $b`,\n\t\t\texpectedStdout: \"1 2\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteRedirection(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\tpathobj, err := ioutil.TempFile(\"\", \"nash-redir\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpath := strings.Replace(pathobj.Name(), \"\\\\\", \"\\\\\\\\\", -1)\n\tdefer os.Remove(path)\n\n\terr = shell.Exec(\"redirect\", fmt.Sprintf(`\n        echo -n \"hello world\" > %s\n        `, path))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(content) != \"hello world\" {\n\t\tt.Fatalf(\"File differ: '%s' != '%s'\", string(content), \"hello world\")\n\t}\n\n\t// Test redirection truncate the file\n\terr = shell.Exec(\"redirect\", fmt.Sprintf(`\n        echo -n \"a\" > %s\n        `, path))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err = ioutil.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(content) != \"a\" {\n\t\tt.Fatalf(\"File differ: '%s' != '%s'\", string(content), \"a\")\n\t}\n\n\t// Test redirection to variable\n\terr = shell.Exec(\"redirect\", `\n\tvar location = \"`+path+`\"\n        echo -n \"hello world\" > $location\n        `)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err = ioutil.ReadFile(path)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(content) != \"hello world\" {\n\t\tt.Errorf(\"File differ: '%s' != '%s'\", string(content), \"hello world\")\n\t\treturn\n\t}\n\n\t// Test redirection to concat\n\terr = shell.Exec(\"redirect\", fmt.Sprintf(`\n\tlocation = \"%s\"\nvar a = \".2\"\n        echo -n \"hello world\" > $location+$a\n        `, path))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(path + \".2\")\n\tcontent, err = ioutil.ReadFile(path + \".2\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(content) != \"hello world\" {\n\t\tt.Fatalf(\"File differ: '%s' != '%s'\", string(content), \"hello world\")\n\t}\n}\n\nfunc TestExecuteRedirectionMap(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\ttmpfile, err := ioutil.TempFile(\"\", \"nash-redir-map\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t//path := strings.Replace(tmpfile.Name(), \"\\\\\", \"\\\\\\\\\", -1)\n\tdefer os.Remove(tmpfile.Name())\n\n\terr = shell.Exec(\"redirect map\", fmt.Sprintf(`\n        echo -n \"hello world\" > %s\n        `, tmpfile.Name()))\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tcontent, err := ioutil.ReadFile(tmpfile.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif string(content) != \"hello world\" {\n\t\tt.Fatalf(\"File differ: '%s' != '%s'\", string(content), \"hello world\")\n\t}\n}\n\nfunc TestExecuteSetenv(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"test setenv basic\",\n\t\t\tcode: `var setenvtest = \"hello\"\n\t\t\t\t\t\t setenv setenvtest\n                         ` + f.nashdPath + ` -c \"echo $setenvtest\"`,\n\t\t\texpectedStdout: \"hello\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test setenv assignment\",\n\t\t\tcode: `setenv setenvtest = \"hello\"\n                         ` + f.nashdPath + ` -c \"echo $setenvtest\"`,\n\t\t\texpectedStdout: \"hello\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test setenv exec cmd\",\n\t\t\tcode: `setenv setenvtest <= echo -n \"hello\"\n                         ` + f.nashdPath + ` -c \"echo $setenvtest\"`,\n\t\t\texpectedStdout: \"hello\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"test setenv semicolon\",\n\t\t\tcode:           `setenv a setenv b`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"test setenv semicolon:1:9: Unexpected token setenv, expected semicolon (;) or EOL\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteCd(t *testing.T) {\n\ttmpdir, err := ioutil.TempDir(\"\", \"nash-cd\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttmpdir, err = filepath.EvalSymlinks(tmpdir)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttmpdirEscaped := strings.Replace(tmpdir, \"\\\\\", \"\\\\\\\\\", -1)\n\thomeEnvVar := \"HOME\"\n\tif runtime.GOOS == \"windows\" {\n\t\thomeEnvVar = \"HOMEPATH\"\n\n\t\t// hack to use nash's pwd instead of gnu on windows\n\t\tprojectDir := filepath.FromSlash(tests.Projectpath)\n\t\tpwdDir := filepath.Join(projectDir, \"stdbin\", \"pwd\")\n\t\tpath := os.Getenv(\"Path\")\n\t\tdefer os.Setenv(\"Path\", path) // TODO(i4k): very unsafe\n\t\tos.Setenv(\"Path\", pwdDir+\";\"+path)\n\t}\n\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"test cd 1\",\n\t\t\tcode: fmt.Sprintf(`cd %s\n        pwd`, tmpdir),\n\t\t\texpectedStdout: tmpdir + \"\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test cd 2\",\n\t\t\tcode: fmt.Sprintf(`%s = \"%s\"\n        setenv %s\n        cd\n        pwd`, homeEnvVar, tmpdirEscaped, homeEnvVar),\n\t\t\texpectedStdout: tmpdir + \"\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test cd into $var\",\n\t\t\tcode: fmt.Sprintf(`\n        var v = \"%s\"\n        cd $v\n        pwd`, tmpdirEscaped),\n\t\t\texpectedStdout: tmpdir + \"\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttest := test\n\t\t\ttestInteractiveExec(t, test)\n\t\t})\n\t}\n}\n\nfunc TestExecuteImport(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\ttmpfile, err := ioutil.TempFile(\"\", \"nash-import\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer os.Remove(tmpfile.Name())\n\n\t_, err = tmpfile.Write([]byte(`var TESTE=\"teste\"`))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfnameEscaped := strings.Replace(tmpfile.Name(), \"\\\\\", \"\\\\\\\\\", -1)\n\n\terr = shell.Exec(\"test import\", fmt.Sprintf(`import %s\n        echo $TESTE\n        `, fnameEscaped))\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif strings.TrimSpace(string(out.Bytes())) != \"teste\" {\n\t\tt.Error(\"Import does not work\")\n\t\treturn\n\t}\n}\n\nfunc TestExecuteIfEqual(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"if equal\",\n\t\t\tcode: `\n        if \"\" == \"\" {\n            echo \"empty string works\"\n        }`,\n\t\t\texpectedStdout: \"empty string works\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if equal\",\n\t\t\tcode: `\n        if \"i4k\" == \"_i4k_\" {\n            echo \"do not print\"\n        }`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if lvalue concat\",\n\t\t\tcode: `\n        if \"i4\"+\"k\" == \"i4k\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if lvalue concat\",\n\t\t\tcode: `var name = \"something\"\n        if $name+\"k\" == \"somethingk\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if lvalue concat\",\n\t\t\tcode: `var name = \"something\"\n        if $name+\"k\"+\"k\" == \"somethingkk\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if rvalue concat\",\n\t\t\tcode: `\n        if \"i4k\" == \"i4\"+\"k\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if lvalue funcall\",\n\t\t\tcode: `var a = ()\n        if len($a) == \"0\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if rvalue funcall\",\n\t\t\tcode: `var a = (\"1\")\n        if \"1\" == len($a) {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"if lvalue funcall with concat\",\n\t\t\tcode: `var a = ()\n        if len($a)+\"1\" == \"01\" {\n            echo -n \"ok\"\n        }`,\n\t\t\texpectedStdout: \"ok\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteIfElse(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test if else\", `\n        if \"\" == \"\" {\n            echo \"if still works\"\n        } else {\n            echo \"nop\"\n        }`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif strings.TrimSpace(string(out.Bytes())) != \"if still works\" {\n\t\tt.Errorf(\"'%s' != 'if still works'\", strings.TrimSpace(string(out.Bytes())))\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"test if equal 2\", `\n        if \"i4k\" == \"_i4k_\" {\n            echo \"do not print\"\n        } else {\n            echo \"print this\"\n        }`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif strings.TrimSpace(string(out.Bytes())) != \"print this\" {\n\t\tt.Errorf(\"Error: '%s' != 'print this'\", strings.TrimSpace(string(out.Bytes())))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteIfElseIf(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test if else\", `\n        if \"\" == \"\" {\n            echo \"if still works\"\n        } else if \"bleh\" == \"bloh\" {\n            echo \"nop\"\n        }`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif strings.TrimSpace(string(out.Bytes())) != \"if still works\" {\n\t\tt.Errorf(\"'%s' != 'if still works'\", strings.TrimSpace(string(out.Bytes())))\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"test if equal 2\", `\n        if \"i4k\" == \"_i4k_\" {\n            echo \"do not print\"\n        } else if \"a\" != \"b\" {\n            echo \"print this\"\n        }`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif strings.TrimSpace(string(out.Bytes())) != \"print this\" {\n\t\tt.Errorf(\"Error: '%s' != 'print this'\", strings.TrimSpace(string(out.Bytes())))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteFnDecl(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\terr := f.shell.Exec(\"test fnDecl\", `\n        fn build(image, debug) {\n                ls\n        }`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n}\n\nfunc TestExecuteFnInv(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test fn inv\", `\nfn getints() {\n        return (\"1\" \"2\" \"3\" \"4\" \"5\" \"6\" \"7\" \"8\" \"9\" \"0\")\n}\n\nvar integers <= getints()\necho -n $integers\n`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"1 2 3 4 5 6 7 8 9 0\" {\n\t\tt.Errorf(\"'%s' != '%s'\", string(out.Bytes()), \"1 2 3 4 5 6 7 8 9 0\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\t// Test fn scope\n\terr = shell.Exec(\"test fn inv\", `\nvar OUTSIDE = \"some value\"\n\nfn getOUTSIDE() {\n        return $OUTSIDE\n}\n\nvar val <= getOUTSIDE()\necho -n $val\n`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"some value\" {\n\t\tt.Errorf(\"'%s' != '%s'\", string(out.Bytes()), \"some value\")\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"test fn inv\", `\nfn notset() {\n        var INSIDE = \"camshaft\"\n}\n\nnotset()\necho -n $INSIDE\n`)\n\tif err == nil {\n\t\tt.Error(\"Must fail\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\t// test variables shadow the global ones\n\terr = shell.Exec(\"test shadow\", `var _path=\"AAA\"\nfn test(_path) {\necho -n $_path\n}\n        test(\"BBB\")\n`)\n\n\tif string(out.Bytes()) != \"BBB\" {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", string(out.Bytes()), \"BBB\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"test shadow\", `\nfn test(_path) {\necho -n $_path\n}\n\n_path=\"AAA\"\n        test(\"BBB\")\n`)\n\n\tif string(out.Bytes()) != \"BBB\" {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", string(out.Bytes()), \"BBB\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\terr = shell.Exec(\"test fn list arg\", `\n\tvar ids_luns = ()\n\tvar id = \"1\"\n\tvar lun = \"lunar\"\n\tvar ids_luns <= append($ids_luns, ($id $lun))\n\tprint(len($ids_luns))`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tgot := string(out.Bytes())\n\texpected := \"1\"\n\tif got != expected {\n\t\tt.Fatalf(\"String differs: '%s' != '%s'\", got, expected)\n\t}\n\n}\n\nfunc TestFnComposition(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"composition\",\n\t\t\tcode: `\n                fn a(b) { echo -n $b }\n                fn b()  { return \"hello\" }\n                a(b())\n        `,\n\t\t\texpectedStdout: \"hello\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"composition\",\n\t\t\tcode: `\n                fn a(b, c) { echo -n $b $c  }\n                fn b()     { return \"hello\" }\n                fn c()     { return \"world\" }\n                a(b(), c())\n        `,\n\t\t\texpectedStdout: \"hello world\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteFnInvOthers(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test fn inv\", `\nfn _getints() {\n        return (\"1\" \"2\" \"3\" \"4\" \"5\" \"6\" \"7\" \"8\" \"9\" \"0\")\n}\n\nfn getints() {\n        var values <= _getints()\n\n        return $values\n}\n\nvar integers <= getints()\necho -n $integers\n`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"1 2 3 4 5 6 7 8 9 0\" {\n\t\tt.Errorf(\"'%s' != '%s'\", string(out.Bytes()), \"1 2 3 4 5 6 7 8 9 0\")\n\t\treturn\n\t}\n}\n\nfunc TestNonInteractive(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\tshell.SetInteractive(true)\n\n\ttestShellExec(t, shell, execTestCase{\n\t\tdesc: \"test bindfn interactive\",\n\t\tcode: `\n        fn greeting() {\n                echo \"Hello\"\n        }\n\n        bindfn greeting hello`,\n\t})\n\n\tshell.SetInteractive(false)\n\t// FIXME: using private stuff on tests ?\n\t// shell.filename = \"<non-interactive>\"\n\tt.Skip(\"FIXME: TEST USES PRIVATE STUFF\")\n\n\texpectedErr := \"<non-interactive>:1:0: \" +\n\t\t\"'hello' is a bind to 'greeting'.\" +\n\t\t\" No binds allowed in non-interactive mode.\"\n\n\ttestShellExec(t, shell, execTestCase{\n\t\tdesc:           \"test 'binded' function non-interactive\",\n\t\tcode:           `hello`,\n\t\texpectedStdout: \"\",\n\t\texpectedStderr: \"\",\n\t\texpectedErr:    expectedErr,\n\t})\n\n\texpectedErr = \"<non-interactive>:6:8: 'bindfn' is not allowed in\" +\n\t\t\" non-interactive mode.\"\n\n\ttestShellExec(t, shell,\n\t\texecTestCase{\n\t\t\tdesc: \"test bindfn non-interactive\",\n\t\t\tcode: `\n        fn goodbye() {\n                echo \"Ciao\"\n        }\n\n        bindfn goodbye ciao`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    expectedErr,\n\t\t})\n}\n\nfunc TestExecuteBindFn(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"test bindfn\",\n\t\t\tcode: `\n\t\t\t\tfn cd() {\n\t\t\t\t\techo \"override builtin cd\"\n\t\t\t\t}\n\n\t\t\t\tbindfn cd cd\n\t\t\t\tcd\n\t\t\t`,\n\t\t\texpectedStdout: \"override builtin cd\\n\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test bindfn vargs\",\n\t\t\tcode: `\n\t\t\t\tfn echoargs(args...) {\n\t\t\t\t\tfor a in $args {\n\t\t\t\t\t\techo $a\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbindfn echoargs echoargs\n\t\t\t\techoargs\n\t\t\t\techoargs \"a\"\n\t\t\t\techoargs \"b\" \"c\"\n\t\t\t`,\n\t\t\texpectedStdout: \"a\\nb\\nc\\n\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test empty bindfn vargs len\",\n\t\t\tcode: `\n\t\t\t\tfn echoargs(args...) {\n\t\t\t\t\tvar l <= len($args)\n\t\t\t\t\techo $l\n\t\t\t\t}\n\n\t\t\t\tbindfn echoargs echoargs\n\t\t\t\techoargs\n\t\t\t`,\n\t\t\texpectedStdout: \"0\\n\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test bindfn args\",\n\t\t\tcode: `\n\t\t\t\tfn foo(line) {\n\t\t\t\t\techo $line\n\t\t\t\t}\n\n\t\t\t\tbindfn foo bar\n\t\t\t\tbar test test\n\t\t\t`,\n\t\t\texpectedErr: \"Wrong number of arguments for function foo. Expected 1 but found 2\",\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestInteractiveExec(t, test)\n\t\t})\n\t}\n}\n\nfunc TestExecutePipe(t *testing.T) {\n\tvar stderr bytes.Buffer\n\tvar stdout bytes.Buffer\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\t// Case 1\n\tcmd := exec.Command(f.nashdPath, \"-c\", `echo hello | tr -d \"[:space:]\"`)\n\n\tcmd.Stderr = &stderr\n\tcmd.Stdout = &stdout\n\n\terr := cmd.Run()\n\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %s\", err.Error())\n\t}\n\n\texpectedOutput := \"hello\"\n\tactualOutput := string(stdout.Bytes())\n\n\tif actualOutput != expectedOutput {\n\t\tt.Errorf(\"'%s' != '%s'\", actualOutput, expectedOutput)\n\t\treturn\n\t}\n\tstdout.Reset()\n\tstderr.Reset()\n\n\t// Case 2\n\tcmd = exec.Command(f.nashdPath, \"-c\", `echo hello | wc -l | tr -d \"[:space:]\"`)\n\n\tcmd.Stderr = &stderr\n\tcmd.Stdout = &stdout\n\n\terr = cmd.Run()\n\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error: %s\", err.Error())\n\t}\n\n\texpectedOutput = \"1\"\n\tactualOutput = string(stdout.Bytes())\n\n\tif actualOutput != expectedOutput {\n\t\tt.Errorf(\"'%s' != '%s'\", actualOutput, expectedOutput)\n\t\treturn\n\t}\n}\n\nfunc TestExecuteRedirectionPipe(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\terr := f.shell.Exec(\"test\", `cat stuff >[2=] | grep file`)\n\texpectedErr := \"<interactive>:1:16: exit status 1|success\"\n\n\tif err == nil {\n\t\tt.Fatalf(\"expected err[%s]\", expectedErr)\n\t}\n\n\tif err.Error() != expectedErr {\n\t\tt.Errorf(\"Expected stderr to be '%s' but got '%s'\",\n\t\t\texpectedErr,\n\t\t\terr.Error())\n\t\treturn\n\t}\n}\n\nfunc testTCPRedirection(t *testing.T, port, command string) {\n\tmessage := \"hello world\"\n\tdone := make(chan error)\n\n\tl, err := net.Listen(\"tcp\", port)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\tgo func() {\n\t\tf, teardown := setup(t)\n\t\tdefer teardown()\n\n\t\terr := <-done\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdone <- f.shell.Exec(\"test net redirection\", command)\n\t}()\n\n\tdone <- nil // synchronize peers\n\tconn, err := l.Accept()\n\tif err != nil {\n\t\tdone <- err\n\t\tt.Fatal(err)\n\t}\n\n\tdefer conn.Close()\n\terr = <-done\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tbuf, err := ioutil.ReadAll(conn)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif msg := string(buf[:]); msg != message {\n\t\tt.Fatalf(\"Unexpected message:\\nGot:\\t\\t%s\\nExpected:\\t%s\\n\", msg, message)\n\t}\n}\n\nfunc TestTCPRedirection(t *testing.T) {\n\ttestTCPRedirection(t, \":4666\", `echo -n \"hello world\" >[1] \"tcp://localhost:4666\"`)\n\ttestTCPRedirection(t, \":4667\", `echo -n \"hello world\" > \"tcp://localhost:4667\"`)\n}\n\nfunc TestExecuteUnixRedirection(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"windows does not support unix socket\")\n\t\treturn\n\t}\n\tmessage := \"hello world\"\n\n\tsockDir, err := ioutil.TempDir(\"\", \"nash-tests\")\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tsockFile := sockDir + \"/listen.sock\"\n\n\tdefer func() {\n\t\tos.Remove(sockFile)\n\t\tos.RemoveAll(sockDir)\n\t}()\n\n\tdone := make(chan bool)\n\twriteDone := make(chan bool)\n\n\tgo func() {\n\n\t\tf, teardown := setup(t)\n\t\tdefer teardown()\n\n\t\tdefer func() {\n\t\t\twriteDone <- true\n\t\t}()\n\n\t\t<-done\n\n\t\terr = f.shell.Exec(\"test net redirection\", `echo -n \"`+message+`\" >[1] \"unix://`+sockFile+`\"`)\n\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t}()\n\n\tl, err := net.Listen(\"unix\", sockFile)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tdefer l.Close()\n\n\tgo func() {\n\t\tconn, err := l.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdefer conn.Close()\n\n\t\tbuf, err := ioutil.ReadAll(conn)\n\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfmt.Println(string(buf[:]))\n\n\t\tif msg := string(buf[:]); msg != message {\n\t\t\tt.Fatalf(\"Unexpected message:\\nGot:\\t\\t%s\\nExpected:\\t%s\\n\", msg, message)\n\t\t}\n\n\t\treturn // Done\n\t}()\n\n\tdone <- true\n\t<-writeDone\n}\n\nfunc TestExecuteUDPRedirection(t *testing.T) {\n\tmessage := \"hello world\"\n\n\tdone := make(chan bool)\n\twriteDone := make(chan bool)\n\n\tgo func() {\n\t\tf, teardown := setup(t)\n\t\tdefer teardown()\n\n\t\tdefer func() {\n\t\t\twriteDone <- true\n\t\t}()\n\n\t\t<-done\n\n\t\terr := f.shell.Exec(\"test net redirection\", `echo -n \"`+message+`\" >[1] \"udp://localhost:6667\"`)\n\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\t}()\n\n\tserverAddr, err := net.ResolveUDPAddr(\"udp\", \":6667\")\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tl, err := net.ListenUDP(\"udp\", serverAddr)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgo func() {\n\t\tdefer l.Close()\n\n\t\tbuf := make([]byte, 1024)\n\n\t\tnb, _, err := l.ReadFromUDP(buf)\n\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\treceived := string(buf[:nb])\n\n\t\tif received != message {\n\t\t\tt.Errorf(\"Unexpected message:\\nGot:\\t\\t'%s'\\nExpected:\\t'%s'\\n\", received, message)\n\t\t}\n\t}()\n\n\ttime.Sleep(time.Second * 1)\n\n\tdone <- true\n\t<-writeDone\n}\n\nfunc TestExecuteReturn(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc:           \"return invalid\",\n\t\t\tcode:           `return`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"<interactive>:1:0: Unexpected return outside of function declaration.\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test simple return\",\n\t\t\tcode: `fn test() { return }\ntest()`,\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"return must finish func evaluation\",\n\t\t\tcode: `fn test() {\n\tif \"1\" == \"1\" {\n\t\treturn \"1\"\n\t}\n\n\treturn \"0\"\n}\n\nvar res <= test()\necho -n $res`,\n\t\t\texpectedStdout: \"1\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"ret from for\",\n\t\t\tcode: `fn test() {\n\tvar values = (0 1 2 3 4 5 6 7 8 9)\n\n\tfor i in $values {\n\t\tif $i == \"5\" {\n\t\t\treturn $i\n\t\t}\n\t}\n\n\treturn \"0\"\n}\nvar a <= test()\necho -n $a`,\n\t\t\texpectedStdout: \"5\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"inf loop ret\",\n\t\t\tcode: `fn test() {\n\tfor {\n\t\tif \"1\" == \"1\" {\n\t\t\treturn \"1\"\n\t\t}\n\t}\n\n\t# never happen\n\treturn \"bleh\"\n}\nvar a <= test()\necho -n $a`,\n\t\t\texpectedStdout: \"1\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"test returning funcall\",\n\t\t\tcode: `fn a() { return \"1\" }\n                         fn b() { return a() }\n                         var c <= b()\n                         echo -n $c`,\n\t\t\texpectedStdout: \"1\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc TestExecuteFnAsFirstClass(t *testing.T) {\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test fn by arg\", `\n        fn printer(val) {\n                echo -n $val\n        }\n\n        fn success(print, val) {\n                $print(\"[SUCCESS] \" + $val)\n        }\n\n        success($printer, \"Command executed!\")\n        `)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected := `[SUCCESS] Command executed!`\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"Differs: '%s' != '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteConcat(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"\", `var a = \"A\"\nvar b = \"B\"\nvar c = $a + $b + \"C\"\necho -n $c`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"ABC\" {\n\t\tt.Errorf(\"Must be equal. '%s' != '%s'\", string(out.Bytes()), \"ABC\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"concat indexed var\", `var tag = (Name some)\n\techo -n \"Key=\"+$tag[0]+\",Value=\"+$tag[1]`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected := \"Key=Name,Value=some\"\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteFor(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"simple loop\", `var files = (/etc/passwd /etc/shells)\nfor f in $files {\n        echo $f\n        echo \"loop\"\n}`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected := `/etc/passwd\nloop\n/etc/shells\nloop`\n\tvalue := strings.TrimSpace(string(out.Bytes()))\n\n\tif value != expected {\n\t\tt.Errorf(\"String differs: '%s' != '%s'\", expected, value)\n\t\treturn\n\t}\n\n}\n\nfunc TestExecuteInfiniteLoop(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\tdoneCtrlc := make(chan bool)\n\tdoneLoop := make(chan bool)\n\n\tgo func() {\n\t\tfmt.Printf(\"Waiting 2 second to abort infinite loop\")\n\t\ttime.Sleep(2 * time.Second)\n\n\t\terr := shell.TriggerCTRLC()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdoneCtrlc <- true\n\t}()\n\n\tgo func() {\n\t\terr := shell.Exec(\"simple loop\", `for {\n\t\techo \"infinite loop\" >[1=]\n\t\tsleep 1\n}`)\n\t\tdoneLoop <- true\n\n\t\tif err == nil {\n\t\t\tt.Errorf(\"Must fail with interrupted error\")\n\t\t\treturn\n\t\t}\n\n\t\ttype interrupted interface {\n\t\t\tInterrupted() bool\n\t\t}\n\n\t\tif errInterrupted, ok := err.(interrupted); !ok || !errInterrupted.Interrupted() {\n\t\t\tt.Errorf(\"Loop not interrupted properly\")\n\t\t\treturn\n\t\t}\n\t}()\n\n\tfor i := 0; i < 2; i++ {\n\t\tselect {\n\t\tcase <-doneCtrlc:\n\t\t\tfmt.Printf(\"CTRL-C Sent to subshell\\n\")\n\t\tcase <-doneLoop:\n\t\t\tfmt.Printf(\"Loop finished.\\n\")\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Errorf(\"Failed to stop infinite loop\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestExecuteVariableIndexing(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"indexing\", `var list = (\"1\" \"2\" \"3\")\n        echo -n $list[0]`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tresult := strings.TrimSpace(string(out.Bytes()))\n\texpected := \"1\"\n\n\tif expected != result {\n\t\tt.Errorf(\"Fail: '%s' != '%s'\", expected, result)\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"indexing\", `var i = \"0\"\necho -n $list[$i]`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tresult = strings.TrimSpace(string(out.Bytes()))\n\texpected = \"1\"\n\n\tif expected != result {\n\t\tt.Errorf(\"Fail: '%s' != '%s'\", expected, result)\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"indexing\", `var tmp <= seq 0 2\nvar seq <= split($tmp, \"\\n\")\n\nfor i in $seq {\n    echo -n $list[$i]\n}`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tresult = strings.TrimSpace(string(out.Bytes()))\n\texpected = \"123\"\n\n\tif expected != result {\n\t\tt.Errorf(\"Fail: '%s' != '%s'\", expected, result)\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"indexing\", `echo -n $list[5]`)\n\tif err == nil {\n\t\tt.Error(\"Must fail. Out of bounds\")\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"indexing\", `var a = (\"0\")\necho -n $list[$a[0]]`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tresult = strings.TrimSpace(string(out.Bytes()))\n\texpected = \"1\"\n\n\tif expected != result {\n\t\tt.Errorf(\"Fail: '%s' != '%s'\", expected, result)\n\t\treturn\n\t}\n}\n\nfunc TestExecuteSubShellDoesNotOverwriteparentEnv(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"set env\", `setenv SHELL = \"bleh\"`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"set env from fn\", `fn test() {\n        # test() should not call the setup func in Nash\n}\n\ntest()\n\necho -n $SHELL`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"bleh\" {\n\t\tt.Errorf(\"Differ: '%s' != '%s'\", \"bleh\", string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteInterruptDoesNotCancelLoop(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tshell.TriggerCTRLC()\n\n\ttime.Sleep(time.Second * 1)\n\n\terr := shell.Exec(\"interrupting loop\", `var seq = (1 2 3 4 5)\nfor i in $seq {}`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n}\n\nfunc TestExecuteErrorSuppressionAll(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\terr := shell.Exec(\"-input-\", `var _, status <= command-not-exists`)\n\tif err != nil {\n\t\tt.Errorf(\"Expected to not fail...: %s\", err.Error())\n\t\treturn\n\t}\n\n\t// FIXME: depending on other sh package on the internal sh tests seems very odd\n\tscode, ok := shell.GetLocalvar(\"status\")\n\tif !ok || scode.Type() != shtypes.StringType || scode.String() != strconv.Itoa(sh.ENotFound) {\n\t\tt.Errorf(\"Invalid status code %v\", scode)\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"-input-\", `var _, status <= echo works`)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\t// FIXME: depending on other sh package on the internal sh tests seems very odd\n\tscode, ok = shell.GetLocalvar(\"status\")\n\tif !ok || scode.Type() != shtypes.StringType || scode.String() != \"0\" {\n\t\tt.Errorf(\"Invalid status code %v\", scode)\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"-input-\", `echo works | cmd-does-not-exists`)\n\tif err == nil {\n\t\tt.Errorf(\"Must fail\")\n\t\treturn\n\t}\n\n\texpectedError := `<interactive>:1:11: not started|exec: \"cmd-does-not-exists\": executable file not found in`\n\n\tif !strings.HasPrefix(err.Error(), expectedError) {\n\t\tt.Errorf(\"Unexpected error: %s\", err.Error())\n\t\treturn\n\t}\n}\n\nfunc TestExecuteGracefullyError(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\terr := shell.Exec(\"someinput.sh\", \"(\")\n\tif err == nil {\n\t\tt.Errorf(\"Must fail...\")\n\t\treturn\n\t}\n\n\texpectErr := \"someinput.sh:1:1: Multi-line command not finished. Found EOF but expect ')'\"\n\n\tif err.Error() != expectErr {\n\t\tt.Errorf(\"Expect error: %s, but got: %s\", expectErr, err.Error())\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"input\", \"echo(\")\n\tif err == nil {\n\t\tt.Errorf(\"Must fail...\")\n\t\treturn\n\t}\n\n\tif err.Error() != \"input:1:5: Unexpected token EOF. Expecting STRING, VARIABLE or )\" {\n\t\tt.Errorf(\"Unexpected error: %s\", err.Error())\n\t\treturn\n\t}\n\n}\n\nfunc TestExecuteMultilineCmd(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test\", `(echo -n\n\t\thello\n\t\tworld)`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected := \"hello world\"\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"Expected '%s' but got '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"test\", `(\n                echo -n 1 2 3 4 5 6 7 8 9 10\n                        11 12 13 14 15 16 17 18 19 20\n                )`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected = \"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\"\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"Expected '%s' but got '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteMultilineCmdAssign(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\tout := f.shellOut\n\n\terr := shell.Exec(\"test\", `var val <= (echo -n\n\t\thello\n\t\tworld)\n\n\techo -n $val`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected := \"hello world\"\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"Expected '%s' but got '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.Exec(\"test\", `val <= (\n                echo -n 1 2 3 4 5 6 7 8 9 10\n                        11 12 13 14 15 16 17 18 19 20\n                )\n\t\techo -n $val`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\texpected = \"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\"\n\n\tif expected != string(out.Bytes()) {\n\t\tt.Errorf(\"Expected '%s' but got '%s'\", expected, string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteMultiReturnUnfinished(t *testing.T) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tshell := f.shell\n\n\terr := shell.Exec(\"test\", \"(\")\n\n\tif err == nil {\n\t\tt.Errorf(\"Must fail... Must return an unfinished paren error\")\n\t\treturn\n\t}\n\n\ttype unfinished interface {\n\t\tUnfinished() bool\n\t}\n\n\tif e, ok := err.(unfinished); !ok || !e.Unfinished() {\n\t\tt.Errorf(\"Must fail with unfinished paren error. Got %s\", err.Error())\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"test\", `(\necho`)\n\n\tif err == nil {\n\t\tt.Errorf(\"Must fail... Must return an unfinished paren error\")\n\t\treturn\n\t}\n\n\tif e, ok := err.(unfinished); !ok || !e.Unfinished() {\n\t\tt.Errorf(\"Must fail with unfinished paren error. Got %s\", err.Error())\n\t\treturn\n\t}\n\n\terr = shell.Exec(\"test\", `(\necho hello\nworld`)\n\n\tif err == nil {\n\t\tt.Errorf(\"Must fail... Must return an unfinished paren error\")\n\t\treturn\n\t}\n\n\tif e, ok := err.(unfinished); !ok || !e.Unfinished() {\n\t\tt.Errorf(\"Must fail with unfinished paren error. Got %s\", err.Error())\n\t\treturn\n\t}\n}\n\nfunc TestExecuteVariadicFn(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"println\",\n\t\t\tcode: `fn println(fmt, arg...) {\n\tprint($fmt+\"\\n\", $arg...)\n}\nprintln(\"%s %s\", \"test\", \"test\")`,\n\t\t\texpectedStdout: \"test test\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"lots of args\",\n\t\t\tcode: `fn println(fmt, arg...) {\n\tprint($fmt+\"\\n\", $arg...)\n}\nprintln(\"%s%s%s%s%s%s%s%s%s%s\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\")`,\n\t\t\texpectedStdout: \"12345678910\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t\texpectedErr:    \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"passing list to var arg fn\",\n\t\t\tcode: `fn puts(arg...) { for a in $arg { echo $a } }\n\t\t\t\tvar a = (\"1\" \"2\" \"3\" \"4\" \"5\")\n\t\t\t\tputs($a...)`,\n\t\t\texpectedErr:    \"\",\n\t\t\texpectedStdout: \"1\\n2\\n3\\n4\\n5\\n\",\n\t\t\texpectedStderr: \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"passing empty list to var arg fn\",\n\t\t\tcode: `fn puts(arg...) { for a in $arg { echo $a } }\n\t\t\t\tvar a = ()\n\t\t\t\tputs($a...)`,\n\t\t\texpectedErr:    \"\",\n\t\t\texpectedStdout: \"\",\n\t\t\texpectedStderr: \"\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"... expansion\",\n\t\t\tcode: `var args = (\"plan9\" \"from\" \"outer\" \"space\")\nprint(\"%s %s %s %s\", $args...)`,\n\t\t\texpectedStdout: \"plan9 from outer space\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"literal ... expansion\",\n\t\t\tcode:           `print(\"%s:%s:%s\", (\"a\" \"b\" \"c\")...)`,\n\t\t\texpectedStdout: \"a:b:c\",\n\t\t},\n\t\t{\n\t\t\tdesc:        \"varargs only as last argument\",\n\t\t\tcode:        `fn println(arg..., fmt) {}`,\n\t\t\texpectedErr: \"<interactive>:1:11: Vararg 'arg...' isn't the last argument\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"variadic argument are optional\",\n\t\t\tcode: `fn println(b...) {\n\tfor v in $b {\n\t\tprint($v)\n\t}\n\tprint(\"\\n\")\n}\nprintln()`,\n\t\t\texpectedStdout: \"\\n\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"the first argument isn't optional\",\n\t\t\tcode: `fn a(b, c...) {\n    print($b, $c...)\n}\na(\"test\")`,\n\t\t\texpectedStdout: \"test\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"the first argument isn't optional\",\n\t\t\tcode: `fn a(b, c...) {\n    print($b, $c...)\n}\na()`,\n\t\t\texpectedErr: \"<interactive>:4:0: Wrong number of arguments for function a. Expected at least 1 arguments but found 0\",\n\t\t},\n\t} {\n\t\ttestExec(t, test)\n\t}\n}\n\nfunc setup(t *testing.T) (testFixture, func()) {\n\tdirs := fixture.SetupNashDirs(t)\n\tshell, err := sh.NewAbortShell(dirs.Path, dirs.Root)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar out bytes.Buffer\n\tshell.SetStdout(&out)\n\n\treturn testFixture{\n\t\tshell:     shell,\n\t\tshellOut:  &out,\n\t\tdir:       tests.Testdir,\n\t\tenvDirs:   dirs,\n\t\tnashdPath: tests.Nashcmd,\n\t}, dirs.Cleanup\n}\n\nfunc testExecuteFile(t *testing.T, path, expected string, before string) {\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tif before != \"\" {\n\t\tf.shell.Exec(\"\", before)\n\t}\n\n\terr := f.shell.ExecFile(path)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(f.shellOut.Bytes()) != expected {\n\t\tt.Errorf(\"Wrong command output: '%s' != '%s'\",\n\t\t\tstring(f.shellOut.Bytes()), expected)\n\t\treturn\n\t}\n}\n\nfunc testShellExec(t *testing.T, shell *sh.Shell, testcase execTestCase) {\n\tt.Helper()\n\n\tvar bout bytes.Buffer\n\tvar berr bytes.Buffer\n\tshell.SetStderr(&berr)\n\tshell.SetStdout(&bout)\n\n\terr := shell.Exec(testcase.desc, testcase.code)\n\tif err != nil {\n\t\tif testcase.expectedPrefixErr != \"\" {\n\t\t\tif !strings.HasPrefix(err.Error(), testcase.expectedPrefixErr) {\n\t\t\t\tt.Errorf(\"[%s] Prefix of error differs: Expected prefix '%s' in '%s'\",\n\t\t\t\t\ttestcase.desc,\n\t\t\t\t\ttestcase.expectedPrefixErr,\n\t\t\t\t\terr.Error())\n\t\t\t}\n\t\t} else if err.Error() != testcase.expectedErr {\n\t\t\tt.Errorf(\"[%s] Error differs: Expected '%s' but got '%s'\",\n\t\t\t\ttestcase.desc,\n\t\t\t\ttestcase.expectedErr,\n\t\t\t\terr.Error())\n\t\t}\n\t} else if testcase.expectedErr != \"\" {\n\t\tt.Fatalf(\"Expected error[%s] but got nil\", testcase.expectedErr)\n\t}\n\n\tif testcase.expectedStdout != string(bout.Bytes()) {\n\t\tt.Errorf(\"[%s] Stdout differs: '%s' != '%s'\",\n\t\t\ttestcase.desc,\n\t\t\ttestcase.expectedStdout,\n\t\t\tstring(bout.Bytes()))\n\t\treturn\n\t}\n\n\tif testcase.expectedStderr != string(berr.Bytes()) {\n\t\tt.Errorf(\"[%s] Stderr differs: '%s' != '%s'\",\n\t\t\ttestcase.desc,\n\t\t\ttestcase.expectedStderr,\n\t\t\tstring(berr.Bytes()))\n\t\treturn\n\t}\n\tbout.Reset()\n\tberr.Reset()\n}\n\nfunc testExec(t *testing.T, testcase execTestCase) {\n\tt.Helper()\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\ttestShellExec(t, f.shell, testcase)\n}\n\nfunc testInteractiveExec(t *testing.T, testcase execTestCase) {\n\tt.Helper()\n\n\tf, teardown := setup(t)\n\tdefer teardown()\n\n\tf.shell.SetInteractive(true)\n\ttestShellExec(t, f.shell, testcase)\n}\n"
  },
  {
    "path": "internal/sh/shell_var_test.go",
    "content": "package sh_test\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests\"\n)\n\nfunc TestVarAssign(t *testing.T) {\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc:           \"simple init\",\n\t\t\tcode:           `var a = \"1\"; echo -n $a`,\n\t\t\texpectedStdout: \"1\",\n\t\t},\n\t\t{\n\t\t\tdesc:        \"variable does not exists\",\n\t\t\tcode:        `a = \"1\"; echo -n $a`,\n\t\t\texpectedErr: `<interactive>:1:0: Variable 'a' is not initialized. Use 'var a = <value>'`,\n\t\t},\n\t\t{\n\t\t\tdesc:           \"variable already initialized\",\n\t\t\tcode:           `var a = \"1\"; var a = \"2\"; echo -n $a`,\n\t\t\texpectedStdout: \"2\",\n\t\t},\n\t\t{\n\t\t\tdesc:           \"variable set\",\n\t\t\tcode:           `var a = \"1\"; a = \"2\"; echo -n $a`,\n\t\t\texpectedStdout: \"2\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"global variable set\",\n\t\t\tcode: `var global = \"1\"\n\t\t\t\tfn somefunc() { global = \"2\" }\n\t\t\t\tsomefunc()\n\t\t\t\techo -n $global`,\n\t\t\texpectedStdout: \"2\",\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestExec(t, test)\n\t\t})\n\t}\n}\n\nfunc TestVarExecAssign(t *testing.T) {\n\n\tfor _, test := range []execTestCase{\n\t\t{\n\t\t\tdesc: \"simple exec var\",\n\t\t\tcode: `var heart <= echo -n \"feed both wolves\"\n\t\t\t\techo -n $heart`,\n\t\t\texpectedStdout: \"feed both wolves\",\n\t\t},\n\t\t{\n\t\t\tdesc:        \"var do not exists\",\n\t\t\tcode:        `__a <= echo -n \"fury\"`,\n\t\t\texpectedErr: \"<interactive>:1:0: Variable '__a' is not initialized. Use 'var __a = <value>'\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple var same name\",\n\t\t\tcode: `var a = \"1\"\n\t\t\t\t\tvar a = \"2\"\n\t\t\t\t\tvar a = \"3\"\n\t\t\t\t\techo -n $a`,\n\t\t\texpectedStdout: \"3\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"multiple var same name with exec\",\n\t\t\tcode: `var a <= echo -n \"1\"\n\t\t\t\tvar a <= echo -n \"hello\"\n\t\t\t\techo -n $a`,\n\t\t\texpectedStdout: \"hello\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"first variable is stdout\",\n\t\t\tcode: `var out <= echo -n \"hello\"\n\t\t\t\techo -n $out`,\n\t\t\texpectedStdout: \"hello\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"two variable, first stdout and second is status\",\n\t\t\tcode: `var stdout, status <= echo -n \"bleh\"\n\t\t\techo -n $stdout $status`,\n\t\t\texpectedStdout: \"bleh 0\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"three variables, stdout empty, stderr with data, status\",\n\t\t\tcode: fmt.Sprintf(`var out, err, st <= %s/write/write /dev/stderr \"hello\"\n\t\t\t\t\techo $out\n\t\t\t\t\techo $err\n\t\t\t\t\techo -n $st`, tests.Stdbindir),\n\t\t\texpectedStdout: \"\\nhello\\n0\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"three variables, stdout with data, stderr empty, status\",\n\t\t\tcode: fmt.Sprintf(`var out, err, st <= %s/write/write /dev/stdout \"hello\"\n\t\t\t\t\techo $out\n\t\t\t\t\techo $err\n\t\t\t\t\techo -n $st`, tests.Stdbindir),\n\t\t\texpectedStdout: \"hello\\n\\n0\",\n\t\t},\n\t} {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\ttestExec(t, test)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/sh/util.go",
    "content": "package sh\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/madlambda/nash/sh\"\n)\n\nvar letterRunes = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\nfunc init() {\n\trand.Seed(time.Now().UnixNano())\n}\n\nfunc randRunes(n int) string {\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letterRunes[rand.Intn(len(letterRunes))]\n\t}\n\treturn string(b)\n}\n\nfunc buildenv(e Env) []string {\n\tenv := make([]string, 0, len(e))\n\n\tfor k, v := range e {\n\t\tif v == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif v.Type() != sh.ListType &&\n\t\t\tv.Type() != sh.StringType {\n\t\t\tcontinue\n\t\t}\n\n\t\tif v.Type() == sh.ListType {\n\t\t\tvlist := v.(*sh.ListObj)\n\t\t\tenv = append(env, k+\"=(\"+vlist.String()+\")\")\n\t\t} else {\n\t\t\tvstr := v.(*sh.StrObj)\n\t\t\tenv = append(env, k+\"=\"+vstr.String())\n\t\t}\n\t}\n\n\treturn env\n}\n\nfunc printVar(out io.Writer, name string, val sh.Obj) {\n\tif val.Type() == sh.StringType {\n\t\tvalstr := val.(*sh.StrObj)\n\t\tfmt.Fprintf(out, \"%s = \\\"%s\\\"\\n\", name, valstr.Str())\n\t} else if val.Type() == sh.ListType {\n\t\tvallist := val.(*sh.ListObj)\n\t\tfmt.Fprintf(out, \"%s = (%s)\\n\", name, vallist.String())\n\t}\n}\n\nfunc printEnv(out io.Writer, name string) {\n\tfmt.Fprintf(out, \"setenv %s\\n\", name)\n}\n\nfunc getErrStatus(err error, def string) string {\n\tstatus := def\n\n\tif exiterr, ok := err.(*exec.ExitError); ok {\n\t\tif statusObj, ok := exiterr.Sys().(syscall.WaitStatus); ok {\n\t\t\tstatus = strconv.Itoa(statusObj.ExitStatus())\n\t\t}\n\t}\n\n\treturn status\n}\n\nfunc nashdAutoDiscover() string {\n\tpath, err := os.Readlink(\"/proc/self/exe\")\n\n\tif err != nil {\n\t\tpath = os.Args[0]\n\n\t\tif _, err := os.Stat(path); err != nil {\n\t\t\treturn \"\"\n\t\t}\n\t}\n\n\treturn path\n}\n"
  },
  {
    "path": "internal/sh/util_test.go",
    "content": "package sh\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/sh\"\n)\n\nfunc TestBuildEnv(t *testing.T) {\n\tenv := Env{\n\t\t\"teste\": nil,\n\t}\n\n\tpenv := buildenv(env)\n\tif len(penv) != 0 {\n\t\tt.Errorf(\"Invalid env length\")\n\t\treturn\n\t}\n\n\tenv = Env{\n\t\t\"PATH\": sh.NewStrObj(\"/bin:/usr/bin\"),\n\t}\n\n\tpenv = buildenv(env)\n\n\tif len(penv) != 1 {\n\t\tt.Errorf(\"Invalid env length\")\n\t\treturn\n\t}\n\n\tif penv[0] != \"PATH=/bin:/usr/bin\" {\n\t\tt.Errorf(\"Invalid env value: %s\", penv[0])\n\t\treturn\n\t}\n\n\tenv = Env{\n\t\t\"PATH\": sh.NewListObj([]sh.Obj{\n\t\t\tsh.NewStrObj(\"/bin\"),\n\t\t\tsh.NewStrObj(\"/usr/bin\"),\n\t\t}),\n\t}\n\n\tpenv = buildenv(env)\n\n\tif len(penv) != 1 {\n\t\tt.Errorf(\"Invalid env length\")\n\t\treturn\n\t}\n\n\tif penv[0] != \"PATH=(/bin /usr/bin)\" {\n\t\tt.Errorf(\"Invalid env value: %s\", penv[0])\n\t\treturn\n\t}\n\n\tenv = Env{\n\t\t\"PATH\": sh.NewListObj([]sh.Obj{\n\t\t\tsh.NewStrObj(\"/bin\"),\n\t\t\tsh.NewStrObj(\"/usr/bin\"),\n\t\t}),\n\t\t\"path\": sh.NewStrObj(\"abracadabra\"),\n\t}\n\n\tpenv = buildenv(env)\n\n\tif len(penv) != 2 {\n\t\tt.Errorf(\"Invalid env length\")\n\t\treturn\n\t}\n\n\tsort.Strings(penv)\n\n\tif penv[0] != \"PATH=(/bin /usr/bin)\" {\n\t\tt.Errorf(\"Invalid env value: '%s'\", penv[0])\n\t\treturn\n\t}\n\n\tif penv[1] != \"path=abracadabra\" {\n\t\tt.Errorf(\"Invalid env value: '%s'\", penv[1])\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "internal/testing/fixture/io.go",
    "content": "package fixture\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// Tmpdir creates a temporary dir and returns a function that can be used\n// to remove it after usage. Any error on any operation returns on a Fatal\n// call on the given testing.T.\nfunc Tmpdir(t *testing.T) (string, func()) {\n\tt.Helper()\n\n\tdir, err := ioutil.TempDir(\"\", \"nash-tests\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdir, err = filepath.EvalSymlinks(dir)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn dir, func() {\n\t\terr := os.RemoveAll(dir)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\n// MkdirAll will do the same thing as os.Mkdirall but calling Fatal on\n// the given testing.T if something goes wrong.\nfunc MkdirAll(t *testing.T, nashlib string) {\n\tt.Helper()\n\n\terr := os.MkdirAll(nashlib, os.ModePerm)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// CreateFiles will create all files and its dirs if\n// necessary calling Fatal on the given testing if anything goes wrong.\n//\n// The files contents will be randomly generated strings (not a lot random,\n// just for test purposes) and will be returned on the map that will map\n// the filepath to its contents\nfunc CreateFiles(t *testing.T, filepaths []string) map[string]string {\n\tt.Helper()\n\n\tcreatedFiles := map[string]string{}\n\n\tfor _, f := range filepaths {\n\t\tcontents := CreateFile(t, f)\n\t\tcreatedFiles[f] = contents\n\t}\n\n\treturn createdFiles\n}\n\n// CreateFile will create the file and its dirs if\n// necessary calling Fatal on the given testing if anything goes wrong.\n//\n// The file content will be randomly generated strings (not a lot random,\n// just for test purposes) and will be returned on the map that will map\n// the filepath to its contents.\n//\n// Return the contents generated for the file (and that has been written on it).\nfunc CreateFile(t *testing.T, f string) string {\n\tt.Helper()\n\n\tdir := filepath.Dir(f)\n\tMkdirAll(t, dir)\n\n\tcontents := fmt.Sprintf(\"randomContents=%d\", rand.Int())\n\n\terr := ioutil.WriteFile(f, []byte(contents), 0644)\n\tif err != nil {\n\t\tt.Fatalf(\"error[%s] writing file[%s]\", err, f)\n\t}\n\n\treturn contents\n}\n\nfunc WorkingDir(t *testing.T) string {\n\tt.Helper()\n\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn wd\n}\n\nfunc ChangeDir(t *testing.T, path string) {\n\tt.Helper()\n\n\terr := os.Chdir(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc Chmod(t *testing.T, path string, mode os.FileMode) {\n\tt.Helper()\n\n\terr := os.Chmod(path, mode)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "nash.go",
    "content": "// Package nash provides a library to embed the `nash` scripting language\n// within your program or create your own nash cli.\npackage nash\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/madlambda/nash/ast\"\n\tshell \"github.com/madlambda/nash/internal/sh\"\n\t\"github.com/madlambda/nash/sh\"\n)\n\ntype (\n\t// Shell is the execution engine of the scripting language.\n\tShell struct {\n\t\tinterp *shell.Shell\n\t}\n)\n\nfunc newShell(nashpath string, nashroot string, abort bool) (*Shell, error) {\n\tvar (\n\t\tnash Shell\n\t\terr  error\n\t)\n\n\tif abort {\n\t\tnash.interp, err = shell.NewAbortShell(nashpath, nashroot)\n\t} else {\n\t\tnash.interp, err = shell.NewShell(nashpath, nashroot)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &nash, nil\n}\n\n// New creates a new `nash.Shell` instance.\nfunc New(nashpath string, nashroot string) (*Shell, error) {\n\treturn newShell(nashpath, nashroot, false)\n}\n\n// NewAbort creates a new shell that aborts in case of error on initialization.\n// Useful for tests, to avoid trashing the output log.\nfunc NewAbort(nashpath string, nashroot string) (*Shell, error) {\n\treturn newShell(nashpath, nashroot, true)\n}\n\n// SetDebug enable some logging for debug purposes.\nfunc (nash *Shell) SetDebug(b bool) {\n\tnash.interp.SetDebug(b)\n}\n\n// SetInteractive enables interactive (shell) mode.\nfunc (nash *Shell) SetInteractive(b bool) {\n\tnash.interp.SetInteractive(b)\n}\n\nfunc (nash *Shell) NashPath() string {\n\treturn nash.interp.NashPath()\n}\n\n// Environ returns the set of environment variables in the shell\nfunc (nash *Shell) Environ() shell.Env {\n\treturn nash.interp.Environ()\n}\n\n// GetFn gets the function object.\nfunc (nash *Shell) GetFn(name string) (sh.FnDef, error) {\n\tfnObj, err := nash.interp.GetFn(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fnObj.Fn(), nil\n}\n\n// Prompt returns the environment prompt or the default one\nfunc (nash *Shell) Prompt() string {\n\tvalue, ok := nash.interp.Getenv(\"PROMPT\")\n\n\tif ok {\n\t\treturn value.String()\n\t}\n\n\treturn \"<no prompt> \"\n}\n\n// SetNashdPath sets an alternativa path to nashd\nfunc (nash *Shell) SetNashdPath(path string) {\n\tnash.interp.SetNashdPath(path)\n}\n\n// Exec executes the code specified by string content.\n// By default, nash uses os.Stdin, os.Stdout and os.Stderr as input, output\n// and error file descriptors. You can change it with SetStdin, SetStdout and Stderr,\n// respectively.\n// The path is only used for error line reporting. If content represents a file, then\n// setting path to this filename should improve debugging (or no).\nfunc (nash *Shell) Exec(path, content string) error {\n\treturn nash.interp.Exec(path, content)\n}\n\n// ExecOutput executes the code specified by string content.\n//\n// It behaves like **Exec** with the exception that it will ignore any\n// stdout parameter (and the default os.Stdout) and will return the\n// whole stdout output in memory.\n//\n// This method has no side effects, it will preserve any previously\n// setted stdout, it will only ignore the configured stdout to run\n// the provided script content;\nfunc (nash *Shell) ExecOutput(path, content string) ([]byte, error) {\n\toldstdout := nash.Stdout()\n\tdefer nash.SetStdout(oldstdout)\n\n\tvar output bytes.Buffer\n\tnash.SetStdout(&output)\n\n\terr := nash.interp.Exec(path, content)\n\treturn output.Bytes(), err\n}\n\n// ExecuteString executes the script content.\n// Deprecated: Use Exec instead.\nfunc (nash *Shell) ExecuteString(path, content string) error {\n\treturn nash.interp.Exec(path, content)\n}\n\n// ExecFile executes the script content of the file specified by path\n// and passes as arguments to the script the given args slice.\nfunc (nash *Shell) ExecFile(path string, args ...string) error {\n\tif len(args) > 0 {\n\t\terr := nash.ExecuteString(\"setting args\", `var ARGS = `+args2Nash(args))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Failed to set nash arguments: %s\", err.Error())\n\t\t}\n\t}\n\treturn nash.interp.ExecFile(path)\n}\n\n// ExecuteFile executes the given file.\n// Deprecated: Use ExecFile instead.\nfunc (nash *Shell) ExecuteFile(path string) error {\n\treturn nash.interp.ExecFile(path)\n}\n\n// ExecuteTree executes the given tree.\n// Deprecated: Use ExecTree instead.\nfunc (nash *Shell) ExecuteTree(tr *ast.Tree) ([]sh.Obj, error) {\n\treturn nash.interp.ExecuteTree(tr)\n}\n\n// ExecTree evaluates the given abstract syntax tree.\n// it returns the object result of eval or nil when not applied and error.\nfunc (nash *Shell) ExecTree(tree *ast.Tree) ([]sh.Obj, error) {\n\treturn nash.interp.ExecuteTree(tree)\n}\n\n// SetStdout set the stdout of the nash engine.\nfunc (nash *Shell) SetStdout(out io.Writer) {\n\tnash.interp.SetStdout(out)\n}\n\n// SetStderr set the stderr of nash engine\nfunc (nash *Shell) SetStderr(err io.Writer) {\n\tnash.interp.SetStderr(err)\n}\n\n// SetStdin set the stdin of the nash engine\nfunc (nash *Shell) SetStdin(in io.Reader) {\n\tnash.interp.SetStdin(in)\n}\n\n// Stdin is the interpreter standard input\nfunc (nash *Shell) Stdin() io.Reader { return nash.interp.Stdin() }\n\n// Stdout is the interpreter standard output\nfunc (nash *Shell) Stdout() io.Writer { return nash.interp.Stdout() }\n\n// Stderr is the interpreter standard error\nfunc (nash *Shell) Stderr() io.Writer { return nash.interp.Stderr() }\n\n// Setvar sets or updates the variable in the nash session. It\n// returns true if variable was found and properly updated.\nfunc (nash *Shell) Setvar(name string, value sh.Obj) bool {\n\treturn nash.interp.Setvar(name, value)\n}\n\n// Newvar creates a new variable in the interpreter scope\nfunc (nash *Shell) Newvar(name string, value sh.Obj) {\n\tnash.interp.Newvar(name, value)\n}\n\n// Getvar retrieves a variable from nash session\nfunc (nash *Shell) Getvar(name string) (sh.Obj, bool) {\n\treturn nash.interp.Getvar(name)\n}\n\nfunc args2Nash(args []string) string {\n\tret := \"(\"\n\n\tfor i := 0; i < len(args); i++ {\n\t\tret += `\"` + args[i] + `\"`\n\n\t\tif i < (len(args) - 1) {\n\t\t\tret += \" \"\n\t\t}\n\t}\n\n\treturn ret + \")\"\n}\n"
  },
  {
    "path": "nash_test.go",
    "content": "package nash\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/sh\"\n\t\"github.com/madlambda/nash/tests\"\n)\n\n// only testing the public API\n// bypass to internal sh.Shell\n\nfunc TestExecuteFile(t *testing.T) {\n\ttestfile := tests.Testdir + \"/ex1.sh\"\n\n\tvar out bytes.Buffer\n\tshell, cleanup := newTestShell(t)\n\tdefer cleanup()\n\n\tshell.SetNashdPath(tests.Nashcmd)\n\tshell.SetStdout(&out)\n\tshell.SetStderr(os.Stderr)\n\tshell.SetStdin(os.Stdin)\n\n\terr := shell.ExecuteFile(testfile)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"hello world\\n\" {\n\t\tt.Errorf(\"Wrong command output: '%s'\", string(out.Bytes()))\n\t\treturn\n\t}\n}\n\nfunc TestExecuteString(t *testing.T) {\n\tshell, cleanup := newTestShell(t)\n\tdefer cleanup()\n\n\tvar out bytes.Buffer\n\n\tshell.SetStdout(&out)\n\n\terr := shell.ExecuteString(\"-ínput-\", \"echo -n AAA\")\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"AAA\" {\n\t\tt.Errorf(\"Unexpected '%s'\", string(out.Bytes()))\n\t\treturn\n\t}\n\n\tout.Reset()\n\n\terr = shell.ExecuteString(\"-input-\", `\n        PROMPT=\"humpback> \"\n        setenv PROMPT\n        `)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tprompt := shell.Prompt()\n\tif prompt != \"humpback> \" {\n\t\tt.Errorf(\"Invalid prompt = %s\", prompt)\n\t\treturn\n\t}\n\n}\n\nfunc TestSetvar(t *testing.T) {\n\tshell, cleanup := newTestShell(t)\n\tdefer cleanup()\n\n\tshell.Newvar(\"__TEST__\", sh.NewStrObj(\"something\"))\n\n\tvar out bytes.Buffer\n\tshell.SetStdout(&out)\n\n\terr := shell.Exec(\"TestSetvar\", `echo -n $__TEST__`)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif string(out.Bytes()) != \"something\" {\n\t\tt.Errorf(\"Value differ: '%s' != '%s'\", string(out.Bytes()), \"something\")\n\t\treturn\n\t}\n\n\tval, ok := shell.Getvar(\"__TEST__\")\n\n\tif !ok || val.String() != \"something\" {\n\t\tt.Errorf(\"Getvar doesn't work: '%s' != '%s'\", val, \"something\")\n\t\treturn\n\t}\n}\n\nfunc newTestShell(t *testing.T) (*Shell, func()) {\n\tt.Helper()\n\n\tnashpath, pathclean := tmpdir(t)\n\tnashroot, rootclean := tmpdir(t)\n\n\ts, err := NewAbort(nashpath, nashroot)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn s, func() {\n\t\tpathclean()\n\t\trootclean()\n\t}\n}\n\nfunc tmpdir(t *testing.T) (string, func()) {\n\tt.Helper()\n\n\tdir, err := ioutil.TempDir(\"\", \"nash-tests\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn dir, func() {\n\t\terr := os.RemoveAll(dir)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "parser/parse.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"strconv\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/errors\"\n\t\"github.com/madlambda/nash/scanner\"\n\t\"github.com/madlambda/nash/token\"\n)\n\ntype (\n\t// Parser parses an nash file\n\tParser struct {\n\t\tname       string // filename or name of the buffer\n\t\tcontent    string\n\t\tl          *scanner.Lexer\n\t\ttok        *scanner.Token // token saved for lookahead\n\t\topenblocks int\n\n\t\tinsidePipe bool\n\n\t\tkeywordParsers map[token.Token]parserFn\n\t}\n\n\tparserFn func(tok scanner.Token) (ast.Node, error)\n\n\texprConfig struct {\n\t\tallowArg      bool\n\t\tallowVariadic bool\n\t\tallowFuncall  bool\n\t\tallowConcat   bool\n\t}\n)\n\n// NewParser creates a new parser\nfunc NewParser(name, content string) *Parser {\n\tp := &Parser{\n\t\tname:    name,\n\t\tcontent: content,\n\t\tl:       scanner.Lex(name, content),\n\t}\n\n\tp.keywordParsers = map[token.Token]parserFn{\n\t\ttoken.For:     p.parseFor,\n\t\ttoken.If:      p.parseIf,\n\t\ttoken.Fn:      p.parseFnDecl,\n\t\ttoken.Var:     p.parseVar,\n\t\ttoken.Return:  p.parseReturn,\n\t\ttoken.Import:  p.parseImport,\n\t\ttoken.SetEnv:  p.parseSetenv,\n\t\ttoken.Rfork:   p.parseRfork,\n\t\ttoken.BindFn:  p.parseBindFn,\n\t\ttoken.Comment: p.parseComment,\n\t\ttoken.Illegal: p.parseError,\n\t}\n\n\treturn p\n}\n\n// Parse starts the parsing.\nfunc (p *Parser) Parse() (tr *ast.Tree, err error) {\n\tvar root *ast.BlockNode\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tif _, ok := r.(runtime.Error); ok {\n\t\t\t\tpanic(r)\n\t\t\t}\n\n\t\t\terr = r.(error)\n\t\t}\n\t}()\n\n\troot, err = p.parseBlock(1, 0)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttr = ast.NewTree(p.name)\n\ttr.Root = root\n\n\treturn tr, nil\n}\n\n// next returns the next item from lookahead buffer if not empty or\n// from the Lexer\nfunc (p *Parser) next() scanner.Token {\n\tif p.tok != nil {\n\t\tt := p.tok\n\t\tp.tok = nil\n\t\treturn *t\n\t}\n\n\ttok := <-p.l.Tokens\n\n\tif tok.Type() == token.Illegal {\n\t\tpanic(errors.NewError(tok.Value()))\n\t}\n\n\treturn tok\n}\n\n// backup puts the item into the lookahead buffer\nfunc (p *Parser) backup(it scanner.Token) error {\n\tif p.tok != nil {\n\t\tpanic(errors.NewError(\"only one slot for backup/lookahead: %s\", it))\n\t}\n\n\tp.tok = &it\n\n\treturn nil\n}\n\n// ignores the next item\nfunc (p *Parser) ignore() {\n\tif p.tok != nil {\n\t\tp.tok = nil\n\t} else {\n\t\t<-p.l.Tokens\n\t}\n}\n\n// peek gets but do not discards the next item (lookahead)\nfunc (p *Parser) peek() scanner.Token {\n\ti := p.next()\n\tp.tok = &i\n\treturn i\n}\n\nfunc (p *Parser) parseBlock(lineStart, columnStart int) (*ast.BlockNode, error) {\n\tln := ast.NewBlockNode(token.NewFileInfo(lineStart, columnStart))\n\n\tfor {\n\t\tit := p.peek()\n\n\t\tswitch it.Type() {\n\t\tcase token.EOF:\n\t\t\tgoto finish\n\t\tcase token.LBrace:\n\t\t\tp.ignore()\n\n\t\t\treturn nil, newParserError(it, p.name,\n\t\t\t\t\"Unexpected '{'\")\n\t\tcase token.RBrace:\n\t\t\tp.ignore()\n\n\t\t\tif p.openblocks <= 0 {\n\t\t\t\treturn nil, newParserError(it, p.name,\n\t\t\t\t\t\"No block open for close\")\n\t\t\t}\n\n\t\t\tp.openblocks--\n\t\t\treturn ln, nil\n\t\tdefault:\n\t\t\tn, err := p.parseStatement()\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tln.Push(n)\n\t\t}\n\t}\n\nfinish:\n\tif p.openblocks != 0 {\n\t\treturn nil, errors.NewUnfinishedBlockError(p.name, p.peek())\n\t}\n\n\treturn ln, nil\n}\n\nfunc (p *Parser) parseStatement() (ast.Node, error) {\n\tit := p.next()\n\tnext := p.peek()\n\n\tif fn, ok := p.keywordParsers[it.Type()]; ok {\n\t\treturn fn(it)\n\t}\n\n\t// statement starting with ident:\n\t// - fn call\n\t// - variable assignment\n\t// - variable exec assignment\n\t// - Command\n\n\tif isFuncall(it.Type(), next.Type()) {\n\t\treturn p.parseFnInv(it, true)\n\t}\n\n\tif it.Type() == token.Ident {\n\t\tif isAssignment(next.Type()) {\n\t\t\treturn p.parseAssignment(it)\n\t\t}\n\n\t\treturn p.parseCommand(it)\n\t} else if it.Type() == token.Arg {\n\t\treturn p.parseCommand(it)\n\t}\n\n\t// statement starting with '('\n\t// -multiline command (echo hello)\n\tif it.Type() == token.LParen {\n\t\treturn p.parseCommand(it)\n\t}\n\n\treturn nil, newParserError(it, p.name, \"Unexpected token parsing statement '%+v'\", it)\n}\n\nfunc (p *Parser) parseIndexing() (ast.Expr, error) {\n\tit := p.next()\n\n\tif it.Type() != token.Number && it.Type() != token.Variable {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Expected number or variable in index. Found %v\", it)\n\t}\n\n\tvar (\n\t\tindex ast.Expr\n\t\terr   error\n\t)\n\n\tif it.Type() == token.Number {\n\t\t// only supports base10\n\t\tintval, err := strconv.Atoi(it.Value())\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tindex = ast.NewIntExpr(it.FileInfo, intval)\n\t} else {\n\t\tindex, err = p.parseVariable(&it, false)\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tit = p.next()\n\n\tif it.Type() != token.RBrack {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v. Expecting ']'\", it)\n\t}\n\n\treturn index, nil\n}\n\nfunc (p *Parser) parseVariable(tok *scanner.Token, allowVararg bool) (ast.Expr, error) {\n\tvar it scanner.Token\n\n\tif tok == nil {\n\t\tit = p.next()\n\t} else {\n\t\tit = *tok\n\t}\n\n\tif it.Type() != token.Variable {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v. Expected VARIABLE\", it)\n\t}\n\n\tvariadicErr := func(tok scanner.Token) (ast.Node, error) {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token '...'. Varargs allowed only in fn call and fn decl\")\n\t}\n\n\tvarTok := it\n\tit = p.peek()\n\tif it.Type() == token.LBrack {\n\t\tvariable := ast.NewVarExpr(varTok.FileInfo, varTok.Value())\n\t\tp.ignore()\n\t\tindex, err := p.parseIndexing()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tisVariadic := p.peek().Type() == token.Dotdotdot\n\t\tif isVariadic && !allowVararg {\n\t\t\treturn variadicErr(p.peek())\n\t\t}\n\t\tindexedVar := ast.NewIndexVariadicExpr(variable.FileInfo, variable, index, isVariadic)\n\t\tif isVariadic {\n\t\t\tp.ignore()\n\t\t}\n\t\treturn indexedVar, nil\n\t}\n\n\tisVariadic := p.peek().Type() == token.Dotdotdot\n\tif isVariadic {\n\t\tif !allowVararg {\n\t\t\treturn variadicErr(p.peek())\n\t\t}\n\t\tp.ignore()\n\t}\n\n\treturn ast.NewVarVariadicExpr(varTok.FileInfo, varTok.Value(), isVariadic), nil\n}\n\nfunc (p *Parser) parsePipe(first *ast.CommandNode) (ast.Node, error) {\n\tit := p.next()\n\n\tn := ast.NewPipeNode(it.FileInfo, first.IsMulti())\n\tfirst.SetMulti(false)\n\n\tn.AddCmd(first)\n\n\tfor it = p.peek(); it.Type() == token.Ident || it.Type() == token.Arg; it = p.peek() {\n\t\tp.next()\n\t\tcmd, err := p.parseCommand(it)\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tn.AddCmd(cmd.(*ast.CommandNode))\n\n\t\tif !p.insidePipe {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif n.IsMulti() {\n\t\tit = p.peek()\n\t\tif it.Type() != token.RParen {\n\t\t\tif it.Type() == token.EOF {\n\t\t\t\treturn nil, errors.NewUnfinishedCmdError(p.name, it)\n\t\t\t}\n\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected symbol '%s'\", it)\n\t\t}\n\n\t\tp.ignore()\n\t}\n\n\tit = p.peek()\n\n\tif it.Type() == token.RBrace {\n\t\treturn n, nil\n\t}\n\n\tif it.Type() != token.Semicolon {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected symbol %s\", it)\n\t}\n\n\tp.ignore()\n\n\treturn n, nil\n}\n\nfunc (p *Parser) parseCommand(it scanner.Token) (ast.Node, error) {\n\tisMulti := false\n\n\tif it.Type() == token.LParen {\n\t\t// multiline command\n\t\tisMulti = true\n\n\t\tit = p.next()\n\t}\n\n\tif it.Type() != token.Ident && it.Type() != token.Arg {\n\t\tif isMulti && it.Type() == token.EOF {\n\t\t\treturn nil, errors.NewUnfinishedCmdError(p.name, it)\n\t\t}\n\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting IDENT or ARG\", it)\n\t}\n\n\tn := ast.NewCommandNode(it.FileInfo, it.Value(), isMulti)\n\ncmdLoop:\n\tfor {\n\t\tit = p.peek()\n\n\t\tswitch typ := it.Type(); {\n\t\tcase typ == token.RBrace:\n\t\t\tif p.openblocks > 0 {\n\t\t\t\tif p.insidePipe {\n\t\t\t\t\tp.insidePipe = false\n\t\t\t\t}\n\n\t\t\t\treturn n, nil\n\t\t\t}\n\n\t\t\tbreak cmdLoop\n\t\tcase isValidArgument(it):\n\t\t\targ, err := p.getArgument(nil, exprConfig{\n\t\t\t\tallowConcat:   true,\n\t\t\t\tallowArg:      true,\n\t\t\t\tallowVariadic: true,\n\t\t\t\tallowFuncall:  false,\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.AddArg(arg)\n\t\tcase typ == token.Plus:\n\t\t\treturn nil, newParserError(it, p.name,\n\t\t\t\t\"Unexpected '+'\")\n\t\tcase typ == token.Gt:\n\t\t\tp.next()\n\t\t\tredir, err := p.parseRedirection(it)\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.AddRedirect(redir)\n\t\tcase typ == token.Pipe:\n\t\t\tif p.insidePipe {\n\t\t\t\tp.next()\n\t\t\t\t// TODO(i4k): test against pipes and multiline cmds\n\t\t\t\treturn n, nil\n\t\t\t}\n\n\t\t\tp.insidePipe = true\n\t\t\treturn p.parsePipe(n)\n\t\tcase typ == token.EOF:\n\t\t\tbreak cmdLoop\n\t\tcase typ == token.Illegal:\n\t\t\treturn nil, errors.NewError(it.Value())\n\t\tdefault:\n\t\t\tbreak cmdLoop\n\t\t}\n\t}\n\n\tit = p.peek()\n\n\tif isMulti {\n\t\tif it.Type() != token.RParen {\n\t\t\tif it.Type() == token.EOF {\n\t\t\t\treturn nil, errors.NewUnfinishedCmdError(p.name, it)\n\t\t\t}\n\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected symbol '%s'\", it)\n\t\t}\n\n\t\tp.ignore()\n\n\t\tit = p.peek()\n\t}\n\n\tif p.insidePipe {\n\t\tp.insidePipe = false\n\t\treturn n, nil\n\t}\n\n\tif it.Type() != token.Semicolon {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected symbol '%s'\", it)\n\t}\n\n\tp.ignore()\n\n\treturn n, nil\n}\n\nfunc (p *Parser) parseRedirection(it scanner.Token) (*ast.RedirectNode, error) {\n\tvar (\n\t\tlval, rval int = ast.RedirMapNoValue, ast.RedirMapNoValue\n\t\terr        error\n\t)\n\n\tredir := ast.NewRedirectNode(it.FileInfo)\n\n\tit = p.peek()\n\n\tif !isValidArgument(it) && it.Type() != token.LBrack {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token: %v\", it)\n\t}\n\n\t// [\n\tif it.Type() == token.LBrack {\n\t\tp.next()\n\t\tit = p.peek()\n\n\t\tif it.Type() != token.Number {\n\t\t\treturn nil, newParserError(it, p.name, \"Expected lefthand side of redirection map, but found '%s'\",\n\t\t\t\tit.Value())\n\t\t}\n\n\t\tlval, err = strconv.Atoi(it.Value())\n\n\t\tif err != nil {\n\t\t\treturn nil, newParserError(it, p.name, \"Redirection map expects integers. Found: %s\",\n\t\t\t\tit.Value())\n\t\t}\n\n\t\tp.next()\n\t\tit = p.peek()\n\n\t\tif it.Type() != token.Assign && it.Type() != token.RBrack {\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting ASSIGN or ]\",\n\t\t\t\tit)\n\t\t}\n\n\t\t// [xxx=\n\t\tif it.Type() == token.Assign {\n\t\t\tp.next()\n\t\t\tit = p.peek()\n\n\t\t\tif it.Type() != token.Number && it.Type() != token.RBrack {\n\t\t\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting REDIRMAPRSIDE or ]\", it)\n\t\t\t}\n\n\t\t\tif it.Type() == token.Number {\n\t\t\t\trval, err = strconv.Atoi(it.Value())\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, newParserError(it, p.name, \"Redirection map expects integers. Found: %s\", it.Value())\n\t\t\t\t}\n\n\t\t\t\tp.next()\n\t\t\t\tit = p.peek()\n\t\t\t} else {\n\t\t\t\trval = ast.RedirMapSupress\n\t\t\t}\n\t\t}\n\n\t\tif it.Type() != token.RBrack {\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting ]\", it)\n\t\t}\n\n\t\t// [xxx=yyy]\n\n\t\tredir.SetMap(lval, rval)\n\n\t\tp.next()\n\t\tit = p.peek()\n\t}\n\n\tif !isValidArgument(it) {\n\t\tif rval != ast.RedirMapNoValue || lval != ast.RedirMapNoValue {\n\t\t\treturn redir, nil\n\t\t}\n\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting STRING or ARG or VARIABLE\", it)\n\t}\n\n\targ, err := p.getArgument(nil, exprConfig{\n\t\tallowConcat:   true,\n\t\tallowArg:      true,\n\t\tallowVariadic: false,\n\t\tallowFuncall:  false,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tredir.SetLocation(arg)\n\n\treturn redir, nil\n}\n\nfunc (p *Parser) parseImport(importToken scanner.Token) (ast.Node, error) {\n\tit := p.next()\n\n\tif it.Type() != token.Arg && it.Type() != token.String && it.Type() != token.Ident {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting ARG or STRING\", it)\n\t}\n\n\tvar arg *ast.StringExpr\n\n\tif it.Type() == token.String {\n\t\targ = ast.NewStringExpr(it.FileInfo, it.Value(), true)\n\t} else if it.Type() == token.Arg || it.Type() == token.Ident {\n\t\targ = ast.NewStringExpr(it.FileInfo, it.Value(), false)\n\t} else {\n\t\treturn nil, newParserError(it, p.name, \"Parser error: Invalid token '%v' for import path\", it)\n\t}\n\n\tif p.peek().Type() == token.Semicolon {\n\t\tp.ignore()\n\t}\n\n\treturn ast.NewImportNode(importToken.FileInfo, arg), nil\n}\n\nfunc (p *Parser) parseSetenv(it scanner.Token) (ast.Node, error) {\n\tvar (\n\t\tsetenv   *ast.SetenvNode\n\t\tassign   ast.Node\n\t\terr      error\n\t\tfileInfo = it.FileInfo\n\t)\n\n\tit = p.next()\n\tnext := p.peek()\n\n\tif it.Type() != token.Ident {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v, expected identifier\", it)\n\t}\n\n\tif next.Type() == token.Assign || next.Type() == token.AssignCmd {\n\t\tassign, err = p.parseAssignment(it)\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsetenv, err = ast.NewSetenvNode(fileInfo, it.Value(), assign)\n\t} else {\n\t\tsetenv, err = ast.NewSetenvNode(fileInfo, it.Value(), nil)\n\n\t\tif p.peek().Type() != token.Semicolon {\n\t\t\treturn nil, newParserError(p.peek(),\n\t\t\t\tp.name,\n\t\t\t\t\"Unexpected token %v, expected semicolon (;) or EOL\",\n\t\t\t\tp.peek())\n\t\t}\n\n\t\tp.ignore()\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn setenv, nil\n}\n\nfunc (p *Parser) getArgument(tok *scanner.Token, cfg exprConfig) (ast.Expr, error) {\n\tvar (\n\t\terr       error\n\t\tit        scanner.Token\n\t\tisFuncall bool\n\t)\n\n\tif tok != nil {\n\t\tit = *tok\n\t} else {\n\t\tit = p.next()\n\t}\n\tif !isValidArgument(it) {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expected %s, %s, %s or %s\",\n\t\t\tit, token.Ident, token.String, token.Variable, token.Arg)\n\t}\n\n\tfirstToken := it\n\tvar arg ast.Expr\n\n\tif firstToken.Type() == token.Variable {\n\t\tnext := p.peek()\n\n\t\tif cfg.allowFuncall && next.Type() == token.LParen {\n\t\t\targ, err = p.parseFnInv(firstToken, false)\n\t\t\tisFuncall = true\n\t\t} else {\n\t\t\t// makes \"echo $list\" == \"echo $list...\"\n\t\t\targ, err = p.parseVariable(&firstToken, cfg.allowVariadic)\n\t\t}\n\t} else if firstToken.Type() == token.String {\n\t\targ = ast.NewStringExpr(firstToken.FileInfo, firstToken.Value(), true)\n\t} else {\n\t\t// Arg, Ident, Number, Dotdotdot, etc\n\n\t\tnext := p.peek()\n\n\t\tif cfg.allowFuncall && next.Type() == token.LParen {\n\t\t\targ, err = p.parseFnInv(firstToken, false)\n\t\t\tisFuncall = true\n\t\t} else {\n\t\t\targ = ast.NewStringExpr(firstToken.FileInfo, firstToken.Value(), false)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tit = p.peek()\n\tif it.Type() == token.Plus && cfg.allowConcat {\n\t\treturn p.getConcatArg(arg)\n\t}\n\n\tif (firstToken.Type() == token.Arg || firstToken.Type() == token.Ident) && (!cfg.allowArg && !isFuncall) {\n\t\treturn nil, newParserError(it, p.name, \"Unquoted string not allowed at pos %d (%s)\", it.FileInfo, it.Value())\n\t}\n\n\treturn arg, nil\n}\n\nfunc (p *Parser) getConcatArg(firstArg ast.Expr) (ast.Expr, error) {\n\tvar (\n\t\tit    scanner.Token\n\t\tparts []ast.Expr\n\t)\n\n\tparts = append(parts, firstArg)\n\nhasConcat:\n\tit = p.peek()\n\n\tif it.Type() == token.Plus {\n\t\tp.ignore()\n\n\t\targ, err := p.getArgument(nil, exprConfig{\n\t\t\tallowArg:      false,\n\t\t\tallowConcat:   false,\n\t\t\tallowFuncall:  true,\n\t\t\tallowVariadic: false,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tparts = append(parts, arg)\n\t\tgoto hasConcat\n\t}\n\n\treturn ast.NewConcatExpr(token.NewFileInfo(firstArg.Line(), firstArg.Column()), parts), nil\n}\n\nfunc (p *Parser) parseAssignment(ident scanner.Token) (ast.Node, error) {\n\t// we're here\n\t// |\n\t// V\n\t// ident = ...\n\t// ident <= ...\n\t// ident, ident2, ..., identN = ...\n\t// ident, ident2, ..., identN <= ...\n\tit := p.next()\n\n\tif !isAssignment(it.Type()) {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v, expected '=' ,'<=', ',' or '['\", it)\n\t}\n\n\tvar (\n\t\tindex ast.Expr\n\t\terr   error\n\t)\n\n\tif it.Type() == token.LBrack {\n\t\tindex, err = p.parseIndexing()\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tit = p.next()\n\t}\n\n\tnames := []*ast.NameNode{\n\t\tast.NewNameNode(ident.FileInfo, ident.Value(), index),\n\t}\n\n\tif it.Type() != token.Comma {\n\t\tgoto assignOp\n\t}\n\n\tfor it = p.next(); it.Type() == token.Ident; it = p.next() {\n\t\tvar index ast.Expr\n\n\t\tname := it\n\t\tit = p.next()\n\n\t\tif it.Type() == token.LBrack {\n\t\t\tindex, err = p.parseIndexing()\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tit = p.next()\n\t\t}\n\n\t\tnames = append(names, ast.NewNameNode(name.FileInfo, name.Value(), index))\n\n\t\tif it.Type() != token.Comma {\n\t\t\tbreak\n\t\t}\n\t}\n\nassignOp:\n\tif it.Type() != token.AssignCmd && it.Type() != token.Assign {\n\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v, expected ',' '=' or '<='\", it)\n\t}\n\n\tif it.Type() == token.AssignCmd {\n\t\treturn p.parseAssignCmdOut(names)\n\t}\n\n\treturn p.parseAssignValues(names)\n}\n\nfunc (p *Parser) parseList(tok *scanner.Token) (ast.Node, error) {\n\tvar (\n\t\targ ast.Expr\n\t\terr error\n\t\tlit scanner.Token\n\t)\n\n\tif tok != nil {\n\t\tlit = *tok\n\t} else {\n\t\tlit = p.next()\n\t}\n\n\tif lit.Type() != token.LParen {\n\t\treturn nil, newParserError(lit, p.name, \"Unexpected token %v. Expecting (\", lit)\n\t}\n\n\tvar values []ast.Expr\n\n\tit := p.peek()\n\n\tfor isValidArgument(it) || it.Type() == token.LParen {\n\t\tif it.Type() == token.LParen {\n\t\t\targ, err = p.parseList(nil)\n\t\t} else {\n\t\t\targ, err = p.getArgument(nil, exprConfig{\n\t\t\t\tallowArg:      true,\n\t\t\t\tallowConcat:   true,\n\t\t\t\tallowFuncall:  false,\n\t\t\t\tallowVariadic: false,\n\t\t\t})\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tit = p.peek()\n\n\t\tvalues = append(values, arg)\n\t}\n\n\tif it.Type() != token.RParen {\n\t\tif it.Type() == token.EOF {\n\t\t\treturn nil, errors.NewUnfinishedListError(p.name, it)\n\t\t}\n\n\t\treturn nil, newParserError(it, p.name, \"Expected ) but found %s\", it)\n\t}\n\n\tp.ignore()\n\n\tvar isVariadic bool\n\tif p.peek().Type() == token.Dotdotdot {\n\t\tisVariadic = true\n\t\tp.ignore()\n\t}\n\treturn ast.NewListVariadicExpr(lit.FileInfo, values, isVariadic), nil\n}\n\nfunc (p *Parser) parseAssignValues(names []*ast.NameNode) (ast.Node, error) {\n\tvar values []ast.Expr\n\n\tif len(names) == 0 {\n\t\treturn nil, newParserError(p.peek(), p.name, \"parser error: expect names non nil\")\n\t}\n\n\tfor it := p.peek(); isExpr(it.Type()); it = p.peek() {\n\t\tvar (\n\t\t\tvalue ast.Expr\n\t\t\terr   error\n\t\t)\n\n\t\tif it.Type() == token.Variable || it.Type() == token.String {\n\t\t\tvalue, err = p.getArgument(nil, exprConfig{\n\t\t\t\tallowArg:      false,\n\t\t\t\tallowFuncall:  true,\n\t\t\t\tallowVariadic: false,\n\t\t\t\tallowConcat:   true,\n\t\t\t})\n\t\t} else if it.Type() == token.LParen { // list\n\t\t\tvalue, err = p.parseList(nil)\n\t\t} else {\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expecting VARIABLE or STRING or (\", it)\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvalues = append(values, value)\n\n\t\tif p.peek().Type() != token.Comma {\n\t\t\tbreak\n\t\t}\n\n\t\tp.ignore()\n\t}\n\n\tif len(values) == 0 {\n\t\treturn nil, newParserError(p.peek(), p.name, \"Unexpected token %v. Expecting VARIABLE, STRING or (\", p.peek())\n\t} else if len(values) != len(names) {\n\t\treturn nil, newParserError(p.peek(), p.name, \"assignment count mismatch: %d = %d\",\n\t\t\tlen(names), len(values))\n\t}\n\n\tif p.peek().Type() == token.Semicolon {\n\t\tp.ignore()\n\t}\n\n\treturn ast.NewAssignNode(names[0].FileInfo, names, values), nil\n}\n\nfunc (p *Parser) parseAssignCmdOut(identifiers []*ast.NameNode) (ast.Node, error) {\n\tvar (\n\t\texec ast.Node\n\t\terr  error\n\t)\n\n\tit := p.next()\n\n\tif it.Type() != token.Ident && it.Type() != token.Arg && it.Type() != token.Variable && it.Type() != token.LParen {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Invalid token %v. Expected command or function invocation\", it)\n\t}\n\n\tif it.Type() == token.LParen {\n\t\t// command invocation\n\t\texec, err = p.parseCommand(it)\n\t} else {\n\t\tnextIt := p.peek()\n\n\t\tif nextIt.Type() != token.LParen {\n\t\t\t// it == (Ident || Arg)\n\t\t\texec, err = p.parseCommand(it)\n\t\t} else {\n\t\t\t// <ident>()\n\t\t\t// <arg>()\n\t\t\t// <var>()\n\t\t\texec, err = p.parseFnInv(it, true)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(identifiers) == 0 {\n\t\t// should not happen... pray\n\t\tpanic(\"internal error parsing assignment\")\n\t}\n\n\treturn ast.NewExecAssignNode(identifiers[0].FileInfo, identifiers, exec)\n}\n\nfunc (p *Parser) parseRfork(it scanner.Token) (ast.Node, error) {\n\tn := ast.NewRforkNode(it.FileInfo)\n\n\tit = p.next()\n\n\tif it.Type() != token.Ident {\n\t\treturn nil, newParserError(it, p.name, \"rfork requires one or more of the following flags: %s\", ast.RforkFlags)\n\t}\n\n\targ := ast.NewStringExpr(it.FileInfo, it.Value(), false)\n\tn.SetFlags(arg)\n\n\tit = p.peek()\n\n\tif it.Type() == token.LBrace {\n\t\tblockPos := it.FileInfo\n\n\t\tp.ignore() // ignore lookaheaded symbol\n\t\tp.openblocks++\n\n\t\ttree := ast.NewTree(\"rfork block\")\n\t\tr, err := p.parseBlock(blockPos.Line(), blockPos.Column())\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttree.Root = r\n\n\t\tn.SetTree(tree)\n\t}\n\n\tif p.peek().Type() == token.Semicolon {\n\t\tp.ignore()\n\t}\n\n\treturn n, nil\n}\n\nfunc (p *Parser) parseIfExpr() (ast.Node, error) {\n\tit := p.peek()\n\tif it.Type() != token.Ident && it.Type() != token.String &&\n\t\tit.Type() != token.Variable {\n\t\treturn nil, newParserError(it, p.name, \"if requires lhs/rhs of type string, variable or function invocation. Found %v\", it)\n\t}\n\n\treturn p.getArgument(nil, exprConfig{\n\t\tallowArg:      false,\n\t\tallowVariadic: false,\n\t\tallowFuncall:  true,\n\t\tallowConcat:   true,\n\t})\n}\n\nfunc (p *Parser) parseIf(it scanner.Token) (ast.Node, error) {\n\tn := ast.NewIfNode(it.FileInfo)\n\n\tlvalue, err := p.parseIfExpr()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn.SetLvalue(lvalue)\n\n\tit = p.next()\n\n\tif it.Type() != token.Equal && it.Type() != token.NotEqual {\n\t\treturn nil, newParserError(it, p.name, \"Expected comparison, but found %v\", it)\n\t}\n\n\tif it.Value() != \"==\" && it.Value() != \"!=\" {\n\t\treturn nil, newParserError(it, p.name, \"Invalid if operator '%s'. Valid comparison operators are '==' and '!='\",\n\t\t\tit.Value())\n\t}\n\n\tn.SetOp(it.Value())\n\n\trvalue, err := p.parseIfExpr()\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn.SetRvalue(rvalue)\n\n\tit = p.next()\n\n\tif it.Type() != token.LBrace {\n\t\treturn nil, newParserError(it, p.name, \"Expected '{' but found %v\", it)\n\t}\n\n\tp.openblocks++\n\n\tr, err := p.parseBlock(it.Line(), it.Column())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = r\n\tn.SetIfTree(ifTree)\n\n\tit = p.peek()\n\n\tif it.Type() == token.Else {\n\t\tp.next()\n\n\t\telseBlock, elseIf, err := p.parseElse()\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\telseTree := ast.NewTree(\"else tree\")\n\t\telseTree.Root = elseBlock\n\n\t\tn.SetElseif(elseIf)\n\t\tn.SetElseTree(elseTree)\n\t}\n\n\treturn n, nil\n}\n\nfunc (p *Parser) parseFnArgs() ([]*ast.FnArgNode, error) {\n\tvar args []*ast.FnArgNode\n\n\tif p.peek().Type() == token.RParen {\n\t\t// no argument\n\t\tp.ignore()\n\t\treturn args, nil\n\t}\n\n\tfor {\n\t\tit := p.next()\n\t\tif it.Type() == token.Ident {\n\t\t\targName := it.Value()\n\t\t\tisVariadic := false\n\t\t\tif p.peek().Type() == token.Dotdotdot {\n\t\t\t\tisVariadic = true\n\t\t\t\tp.ignore()\n\t\t\t}\n\t\t\targs = append(args, ast.NewFnArgNode(it.FileInfo,\n\t\t\t\targName, isVariadic))\n\t\t} else {\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected token %v. Expected identifier or ')'\", it)\n\t\t}\n\n\t\tit = p.peek()\n\t\tif it.Type() == token.Comma {\n\t\t\tp.ignore()\n\t\t\tit = p.peek()\n\n\t\t\tif it.Type() == token.RParen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif it.Type() != token.RParen {\n\t\t\treturn nil, newParserError(it, p.name, \"Unexpected '%v'. Expected ')'\", it)\n\t\t}\n\n\t\tp.ignore()\n\t\tbreak\n\t}\n\n\treturn args, nil\n}\n\nfunc (p *Parser) parseVar(it scanner.Token) (ast.Node, error) {\n\tvar varTok = it\n\n\tit = p.next()\n\tnext := p.peek()\n\n\tif it.Type() != token.Ident {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v. Expected IDENT\",\n\t\t\tnext,\n\t\t)\n\t}\n\n\tif !isAssignment(next.Type()) {\n\t\treturn nil, newParserError(next, p.name,\n\t\t\t\"Unexpected token %v. Expected '=' or ','\",\n\t\t\tnext,\n\t\t)\n\t}\n\n\tassign, err := p.parseAssignment(it)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch assign.Type() {\n\tcase ast.NodeAssign:\n\t\treturn ast.NewVarAssignDecl(\n\t\t\tvarTok.FileInfo,\n\t\t\tassign.(*ast.AssignNode),\n\t\t), nil\n\tcase ast.NodeExecAssign:\n\t\treturn ast.NewVarExecAssignDecl(\n\t\t\tvarTok.FileInfo,\n\t\t\tassign.(*ast.ExecAssignNode),\n\t\t), nil\n\t}\n\n\treturn nil, newParserError(next, p.name,\n\t\t\"Unexpected token %v. Expected ASSIGN or EXECASSIGN\",\n\t\tnext,\n\t)\n}\n\nfunc (p *Parser) parseFnDecl(it scanner.Token) (ast.Node, error) {\n\tvar n *ast.FnDeclNode\n\n\tit = p.next()\n\tif it.Type() == token.Ident {\n\t\tn = ast.NewFnDeclNode(it.FileInfo, it.Value())\n\t\tit = p.next()\n\t} else {\n\t\tn = ast.NewFnDeclNode(it.FileInfo, \"\")\n\t}\n\n\tif it.Type() != token.LParen {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v. Expected '('\", it)\n\t}\n\n\targs, err := p.parseFnArgs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, arg := range args {\n\t\tn.AddArg(arg)\n\t}\n\n\tit = p.next()\n\tif it.Type() != token.LBrace {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Unexpected token %v. Expected '{'\", it)\n\t}\n\n\tp.openblocks++\n\n\ttree := ast.NewTree(fmt.Sprintf(\"fn %s body\", n.Name()))\n\tr, err := p.parseBlock(it.Line(), it.Column())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttree.Root = r\n\tn.SetTree(tree)\n\treturn n, nil\n}\n\nfunc (p *Parser) parseFnInv(ident scanner.Token, allowSemicolon bool) (ast.Node, error) {\n\tn := ast.NewFnInvNode(ident.FileInfo, ident.Value())\n\n\tit := p.next()\n\tif it.Type() != token.LParen {\n\t\treturn nil, newParserError(it, p.name, \"Invalid token %v. Expected '('\", it)\n\t}\n\n\tfor {\n\t\tit = p.next()\n\t\tnext := p.peek()\n\t\tif isFuncall(it.Type(), next.Type()) ||\n\t\t\tisValidArgument(it) {\n\t\t\targ, err := p.getArgument(&it, exprConfig{\n\t\t\t\tallowArg:      false,\n\t\t\t\tallowFuncall:  true,\n\t\t\t\tallowConcat:   true,\n\t\t\t\tallowVariadic: true,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tn.AddArg(arg)\n\t\t} else if it.Type() == token.LParen {\n\t\t\tlistArg, err := p.parseList(&it)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tn.AddArg(listArg)\n\t\t} else if it.Type() == token.RParen {\n\t\t\t//\t\t\tp.next()\n\t\t\tbreak\n\t\t} else if it.Type() == token.EOF {\n\t\t\tgoto parseError\n\t\t}\n\n\t\tit = p.peek()\n\t\tif it.Type() == token.Comma {\n\t\t\tp.ignore()\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif it.Type() == token.RParen {\n\t\t\tp.next()\n\t\t\tbreak\n\t\t}\n\n\t\tgoto parseError\n\t}\n\n\t// semicolon is optional here\n\tif allowSemicolon && p.peek().Type() == token.Semicolon {\n\t\tp.next()\n\t}\n\n\treturn n, nil\n\nparseError:\n\treturn nil, newParserError(it, p.name,\n\t\t\"Unexpected token %v. Expecting STRING, VARIABLE or )\", it)\n}\n\nfunc (p *Parser) parseElse() (*ast.BlockNode, bool, error) {\n\tit := p.next()\n\n\tif it.Type() == token.LBrace {\n\t\tp.openblocks++\n\n\t\telseBlock, err := p.parseBlock(it.Line(), it.Column())\n\n\t\tif err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\treturn elseBlock, false, nil\n\t}\n\n\tif it.Type() == token.If {\n\t\tifNode, err := p.parseIf(it)\n\n\t\tif err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\tblock := ast.NewBlockNode(it.FileInfo)\n\t\tblock.Push(ifNode)\n\n\t\treturn block, true, nil\n\t}\n\n\treturn nil, false, newParserError(it, p.name, \"Unexpected token: %v\", it)\n}\n\nfunc (p *Parser) parseBindFn(bindIt scanner.Token) (ast.Node, error) {\n\tnameIt := p.next()\n\n\tif nameIt.Type() != token.Ident {\n\t\treturn nil, newParserError(nameIt, p.name,\n\t\t\t\"Expected identifier, but found '%v'\", nameIt)\n\t}\n\n\tcmdIt := p.next()\n\n\tif cmdIt.Type() != token.Ident {\n\t\treturn nil, newParserError(cmdIt, p.name, \"Expected identifier, but found '%v'\", cmdIt)\n\t}\n\n\tif p.peek().Type() == token.Semicolon {\n\t\tp.ignore()\n\t}\n\n\tn := ast.NewBindFnNode(bindIt.FileInfo, nameIt.Value(), cmdIt.Value())\n\treturn n, nil\n}\n\nfunc (p *Parser) parseReturn(retTok scanner.Token) (ast.Node, error) {\n\tret := ast.NewReturnNode(retTok.FileInfo)\n\n\ttok := p.peek()\n\n\t// return;\n\t// return }\n\t// return $v\n\t// return \"<some>\"\n\t// return ( ... values ... )\n\t// return <fn name>()\n\t// return \"val1\", \"val2\", $val3, test()\n\tif tok.Type() != token.Semicolon &&\n\t\ttok.Type() != token.RBrace &&\n\t\ttok.Type() != token.Variable &&\n\t\ttok.Type() != token.String &&\n\t\ttok.Type() != token.LParen &&\n\t\ttok.Type() != token.Ident {\n\t\treturn nil, newParserError(tok, p.name,\n\t\t\t\"Expected ';', STRING, VARIABLE, FUNCALL or LPAREN, but found %v\",\n\t\t\ttok)\n\t}\n\n\tvar returnExprs []ast.Expr\n\n\tfor {\n\t\ttok = p.peek()\n\t\tif tok.Type() == token.Semicolon {\n\t\t\tp.ignore()\n\t\t\tbreak\n\t\t}\n\n\t\tif tok.Type() == token.RBrace {\n\t\t\tbreak\n\t\t}\n\n\t\tif tok.Type() == token.LParen {\n\t\t\tlistArg, err := p.parseList(nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturnExprs = append(returnExprs, listArg)\n\t\t} else if tok.Type() == token.Ident {\n\t\t\tp.next()\n\t\t\tnext := p.peek()\n\n\t\t\tif next.Type() != token.LParen {\n\t\t\t\treturn nil, newParserError(tok, p.name,\n\t\t\t\t\t\"Expected FUNCALL, STRING, VARIABLE or LPAREN, but found '%v' %v\",\n\t\t\t\t\ttok.Value(), next)\n\t\t\t}\n\n\t\t\targ, err := p.parseFnInv(tok, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturnExprs = append(returnExprs, arg)\n\t\t} else {\n\t\t\targ, err := p.getArgument(nil, exprConfig{\n\t\t\t\tallowArg:      false,\n\t\t\t\tallowConcat:   true,\n\t\t\t\tallowFuncall:  true,\n\t\t\t\tallowVariadic: false,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturnExprs = append(returnExprs, arg)\n\t\t}\n\n\t\tnext := p.peek()\n\n\t\tif next.Type() == token.Comma {\n\t\t\tp.ignore()\n\t\t\tcontinue\n\t\t}\n\n\t\tif next.Type() == token.Semicolon {\n\t\t\tp.ignore()\n\t\t}\n\n\t\tbreak\n\t}\n\n\tret.Returns = returnExprs\n\n\treturn ret, nil\n}\n\nfunc (p *Parser) parseFor(it scanner.Token) (ast.Node, error) {\n\tvar (\n\t\tinExpr ast.Expr\n\t\terr    error\n\t\tnext   scanner.Token\n\t)\n\n\tforStmt := ast.NewForNode(it.FileInfo)\n\n\tit = p.peek()\n\n\tif it.Type() != token.Ident {\n\t\tgoto forBlockParse\n\t}\n\n\tp.next()\n\n\tforStmt.SetIdentifier(it.Value())\n\n\tit = p.next()\n\n\tif it.Type() != token.Ident || it.Value() != \"in\" {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Expected 'in' but found %q\", it)\n\t}\n\n\t// ignores 'in' keyword\n\t// TODO: make 'in' a real keyword\n\n\tit = p.next()\n\tnext = p.peek()\n\n\tif it.Type() != token.Variable &&\n\t\t(it.Type() != token.Ident || (it.Type() == token.Ident && next.Type() != token.LParen)) &&\n\t\tit.Type() != token.LParen {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Expected (variable, list or fn invocation) but found %q\", it)\n\t}\n\n\tif (it.Type() == token.Ident || it.Type() == token.Variable) && next.Type() == token.LParen {\n\t\tinExpr, err = p.parseFnInv(it, false)\n\t} else if it.Type() == token.Variable {\n\t\tinExpr, err = p.parseVariable(&it, false)\n\t} else if it.Type() == token.LParen {\n\t\tinExpr, err = p.parseList(&it)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tforStmt.SetInExpr(inExpr)\nforBlockParse:\n\tit = p.peek()\n\n\tif it.Type() != token.LBrace {\n\t\treturn nil, newParserError(it, p.name,\n\t\t\t\"Expected '{' but found %q\", it)\n\t}\n\n\tblockPos := it.FileInfo\n\n\tp.ignore() // ignore lookaheaded symbol\n\tp.openblocks++\n\n\ttree := ast.NewTree(\"for block\")\n\n\tr, err := p.parseBlock(blockPos.Line(), blockPos.Column())\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttree.Root = r\n\tforStmt.SetTree(tree)\n\n\treturn forStmt, nil\n}\n\nfunc (p *Parser) parseComment(it scanner.Token) (ast.Node, error) {\n\treturn ast.NewCommentNode(it.FileInfo, it.Value()), nil\n}\n\nfunc (p *Parser) parseError(it scanner.Token) (ast.Node, error) {\n\treturn nil, errors.NewError(it.Value())\n}\n\nfunc newParserError(item scanner.Token, name, format string, args ...interface{}) error {\n\tif item.Type() == token.Illegal {\n\t\t// scanner error\n\t\treturn errors.NewError(item.Value())\n\t}\n\n\terrstr := fmt.Sprintf(format, args...)\n\n\treturn errors.NewError(\"%s:%d:%d: %s\", name, item.Line(), item.Column(), errstr)\n}\n\nfunc isValidArgument(t scanner.Token) bool {\n\tif t.Type() == token.String ||\n\t\tt.Type() == token.Number ||\n\t\tt.Type() == token.Arg ||\n\t\tt.Type() == token.Dotdotdot ||\n\t\tt.Type() == token.Ident ||\n\t\ttoken.IsKeyword(t.Type()) ||\n\t\tt.Type() == token.Variable {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc isFuncall(tok, next token.Token) bool {\n\treturn (tok == token.Ident || tok == token.Variable) &&\n\t\tnext == token.LParen\n}\n\nfunc isAssignment(tok token.Token) bool {\n\treturn tok == token.Assign ||\n\t\ttok == token.AssignCmd ||\n\t\ttok == token.LBrack ||\n\t\ttok == token.Comma\n}\n\nfunc isExpr(tok token.Token) bool {\n\treturn tok == token.Variable ||\n\t\ttok == token.String ||\n\t\ttok == token.LParen\n}\n"
  },
  {
    "path": "parser/parse_fmt_test.go",
    "content": "package parser\n\nimport \"testing\"\n\ntype fmtTestTable struct {\n\tinput, expected string\n}\n\nfunc testFmt(input string, expected string, t *testing.T) {\n\tp := NewParser(\"fmt test\", input)\n\n\ttree, err := p.Parse()\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tfmtval := tree.String()\n\n\tif fmtval != expected {\n\t\tt.Errorf(\"Fmt differ: '%s' != '%s'\", fmtval, expected)\n\t\treturn\n\t}\n}\n\nfunc testFmtTable(testTable []fmtTestTable, t *testing.T) {\n\tfor _, test := range testTable {\n\t\ttestFmt(test.input, test.expected, t)\n\t}\n}\n\nfunc TestFmtVariables(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\n\t\t// correct adjust of spaces\n\t\t{`test = \"a\"`, `test = \"a\"`},\n\t\t{`test=\"a\"`, `test = \"a\"`},\n\t\t{`test= \"a\"`, `test = \"a\"`},\n\t\t{`test  =\"a\"`, `test = \"a\"`},\n\t\t{`test =    \"a\"`, `test = \"a\"`},\n\t\t{`test\t=\"a\"`, `test = \"a\"`},\n\t\t{`test\t\t=\"a\"`, `test = \"a\"`},\n\t\t{`test =\t\"a\"`, `test = \"a\"`},\n\t\t{`test =\t\t\"a\"`, `test = \"a\"`},\n\t\t{`test = ()`, `test = ()`},\n\t\t{`test=()`, `test = ()`},\n\t\t{`test =()`, `test = ()`},\n\t\t{`test\t=()`, `test = ()`},\n\t\t{`test=\t()`, `test = ()`},\n\t\t{`test = (plan9)`, `test = (plan9)`},\n\t\t{`test=(plan9)`, `test = (plan9)`},\n\t\t{`test      = (plan9)`, `test = (plan9)`},\n\t\t{`test\t= (plan9)`, `test = (plan9)`},\n\t\t{`test\t=\t(plan9)`, `test = (plan9)`},\n\t\t{`test = (\tplan9)`, `test = (plan9)`},\n\t\t{`test = (     plan9)`, `test = (plan9)`},\n\t\t{`test = (plan9     )`, `test = (plan9)`},\n\t\t{`test = (plan9 from bell labs)`, `test = (plan9 from bell labs)`},\n\t\t{`test = (plan9         from bell labs)`, `test = (plan9 from bell labs)`},\n\t\t{`test = (plan9         from         bell         labs)`, `test = (plan9 from bell labs)`},\n\t\t{`test = (plan9\tfrom\tbell\tlabs)`, `test = (plan9 from bell labs)`},\n\t\t{`test = (\n\tplan9\n\tfrom\n\tbell\n\tlabs\n)`, `test = (plan9 from bell labs)`},\n\t\t{`test = (plan9 from bell labs windows linux freebsd netbsd openbsd)`, `test = (\n\tplan9\n\tfrom\n\tbell\n\tlabs\n\twindows\n\tlinux\n\tfreebsd\n\tnetbsd\n\topenbsd\n)`},\n\n\t\t{`IFS = (\"\\n\")`, `IFS = (\"\\n\")`},\n\n\t\t// multiple variables\n\t\t{`test = \"a\"\ntestb = \"b\"`, `test  = \"a\"\ntestb = \"b\"`},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtGroupVariables(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`test = \"a\"\n\ntest2 = \"b\"\n\nfn cd() { echo \"hello\" }`,\n\t\t\t`test  = \"a\"\ntest2 = \"b\"\n\nfn cd() {\n\techo \"hello\"\n}`,\n\t\t},\n\t\t{\n\t\t\t`#!/usr/bin/env nash\necho \"hello\"`,\n\t\t\t`#!/usr/bin/env nash\n\necho \"hello\"`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtFn(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`fn lala() { echo hello }\nfn lele() { echo lele }`,\n\t\t\t`fn lala() {\n\techo hello\n}\n\nfn lele() {\n\techo lele\n}`,\n\t\t},\n\t\t{\n\t\t\t`vv = \"\"\nfn t() {\n\techo t\n}`,\n\t\t\t`vv = \"\"\n\nfn t() {\n\techo t\n}`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtImports(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`import test\nimport test\n\nimport test`,\n\t\t\t`import test\nimport test\nimport test`,\n\t\t},\n\t\t{\n\t\t\t`import nashlib/all\nimport klb/aws/all\n\nvpcTags = ((Name klb-vpc-example) (Env testing))\n`,\n\t\t\t`import nashlib/all\nimport klb/aws/all\n\nvpcTags = (\n\t(Name klb-vpc-example)\n\t(Env testing)\n)`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtFnComments(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`PATH = \"/bin\"\n\n# isolated comment\n\n# Comment for fn\nfn test() {\n\techo \"hello\"\n}\n`,\n\t\t\t`PATH = \"/bin\"\n\n# isolated comment\n\n# Comment for fn\nfn test() {\n\techo \"hello\"\n}`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtSamples(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`#!/usr/bin/env nash\nimport nashlib/all\nimport klb/aws/all\nvpcTags = ((Name klb-vpc-example) (Env testing))\nigwTags = ((Name klb-igw-example) (Env testing))\nrouteTblTags = ((Name klb-rtbl-example) (Env testing))\nappSubnetTags = ((Name klb-app-subnet-example) (Env testing))\ndbSubnetTags = ((Name klb-db-subnet-example) (Env testing))\nsgTags = ((Name klb-sg-example) (Env testing))\nfn print_resource(name, id) {\n\tprintf \"Created %s: %s%s%s\\n\" $name $NASH_GREEN $id $NASH_RESET\n}\nfn create_prod() {\n\tvpcid <= aws_vpc_create(\"10.0.0.1/16\", $vpcTags)\n\tappnet <= aws_subnet_create($vpcid, \"10.0.1.0/24\", $appSubnetTags)\n\tdbnet <= aws_subnet_create($vpcid, \"10.0.2.0/24\", $dbSubnetTags)\n\tigwid <= aws_igw_create($igwTags)\n\ttblid <= aws_routetbl_create($vpcid, $routeTblTags)\n\taws_igw_attach($igwid, $vpcid)\n\taws_route2igw($tblid, \"0.0.0.0/0\", $igwid)\n\tgrpid <= aws_secgroup_create(\"klb-default-sg\", \"sg description\", $vpcid, $sgTags)\n\tprint_resource(\"VPC\", $vpcid)\n\tprint_resource(\"app subnet\", $appnet)\n\tprint_resource(\"db subnet\", $dbnet)\n\tprint_resource(\"Internet Gateway\", $igwid)\n\tprint_resource(\"Routing table\", $tblid)\n\tprint_resource(\"Security group\", $grpid)\n}\ncreate_prod()\n`,\n\t\t\t`#!/usr/bin/env nash\n\nimport nashlib/all\nimport klb/aws/all\n\nvpcTags       = (\n\t(Name klb-vpc-example)\n\t(Env testing)\n)\n\nigwTags       = (\n\t(Name klb-igw-example)\n\t(Env testing)\n)\n\nrouteTblTags  = (\n\t(Name klb-rtbl-example)\n\t(Env testing)\n)\n\nappSubnetTags = (\n\t(Name klb-app-subnet-example)\n\t(Env testing)\n)\n\ndbSubnetTags  = (\n\t(Name klb-db-subnet-example)\n\t(Env testing)\n)\n\nsgTags        = (\n\t(Name klb-sg-example)\n\t(Env testing)\n)\n\nfn print_resource(name, id) {\n\tprintf \"Created %s: %s%s%s\\n\" $name $NASH_GREEN $id $NASH_RESET\n}\n\nfn create_prod() {\n\tvpcid  <= aws_vpc_create(\"10.0.0.1/16\", $vpcTags)\n\tappnet <= aws_subnet_create($vpcid, \"10.0.1.0/24\", $appSubnetTags)\n\tdbnet  <= aws_subnet_create($vpcid, \"10.0.2.0/24\", $dbSubnetTags)\n\tigwid  <= aws_igw_create($igwTags)\n\ttblid  <= aws_routetbl_create($vpcid, $routeTblTags)\n\n\taws_igw_attach($igwid, $vpcid)\n\taws_route2igw($tblid, \"0.0.0.0/0\", $igwid)\n\n\tgrpid <= aws_secgroup_create(\"klb-default-sg\", \"sg description\", $vpcid, $sgTags)\n\n\tprint_resource(\"VPC\", $vpcid)\n\tprint_resource(\"app subnet\", $appnet)\n\tprint_resource(\"db subnet\", $dbnet)\n\tprint_resource(\"Internet Gateway\", $igwid)\n\tprint_resource(\"Routing table\", $tblid)\n\tprint_resource(\"Security group\", $grpid)\n}\n\ncreate_prod()`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n\nfunc TestFmtPipes(t *testing.T) {\n\ttestTable := []fmtTestTable{\n\t\t{\n\t\t\t`echo hello | grep \"he\" > test`,\n\t\t\t`echo hello | grep \"he\" > test`,\n\t\t},\n\t\t{\n\t\t\t`(echo hello | sed \"s/he/wo/g\" >[1] /tmp/test >[2] /dev/null)`,\n\t\t\t`(\n\techo hello |\n\tsed \"s/he/wo/g\"\n\t\t>[1] /tmp/test\n\t\t>[2] /dev/null\n)`,\n\t\t},\n\t\t{\n\t\t\t`choice <= (\n                -find $dir+\"/\" -maxdepth 1 |\n                sed \"s#.*/##\" |\n                sort |\n                uniq |\n                -fzf --exact -q \"^\"+$query -1 -0 --inline-info --header \"select file: \"\n       )`,\n\t\t\t`choice <= (\n\t-find $dir+\"/\"\n\t\t-maxdepth\n\t\t1 |\n\tsed \"s#.*/##\" |\n\tsort |\n\tuniq |\n\t-fzf --exact\n\t\t-q\n\t\t\"^\"+$query -1\n\t\t-0\n\t\t--inline-info\n\t\t--header \"select file: \"\n)`,\n\t\t},\n\t}\n\n\ttestFmtTable(testTable, t)\n}\n"
  },
  {
    "path": "parser/parse_regression_test.go",
    "content": "package parser\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/token\"\n)\n\nfunc init() {\n\tast.DebugCmp = true\n}\n\nfunc TestParseIssue22(t *testing.T) {\n\texpected := ast.NewTree(\"issue 22\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tfn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"gocd\")\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 8), \"path\", false))\n\n\tfnTree := ast.NewTree(\"fn\")\n\tfnBlock := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tifDecl := ast.NewIfNode(token.NewFileInfo(2, 1))\n\tifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(2, 4), \"$path\"))\n\tifDecl.SetOp(\"==\")\n\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(2, 13), \"\", true))\n\n\tifTree := ast.NewTree(\"if\")\n\tifBlock := ast.NewBlockNode(token.NewFileInfo(2, 1))\n\n\tcdNode := ast.NewCommandNode(token.NewFileInfo(3, 2), \"cd\", false)\n\targ := ast.NewVarExpr(token.NewFileInfo(3, 5), \"$GOPATH\")\n\tcdNode.AddArg(arg)\n\n\tifBlock.Push(cdNode)\n\tifTree.Root = ifBlock\n\tifDecl.SetIfTree(ifTree)\n\n\telseTree := ast.NewTree(\"else\")\n\telseBlock := ast.NewBlockNode(token.NewFileInfo(4, 9))\n\n\targs := make([]ast.Expr, 3)\n\targs[0] = ast.NewVarExpr(token.NewFileInfo(5, 5), \"$GOPATH\")\n\targs[1] = ast.NewStringExpr(token.NewFileInfo(5, 12), \"/src/\", true)\n\targs[2] = ast.NewVarExpr(token.NewFileInfo(5, 20), \"$path\")\n\n\tcdNodeElse := ast.NewCommandNode(token.NewFileInfo(5, 2), \"cd\", false)\n\tcarg := ast.NewConcatExpr(token.NewFileInfo(5, 5), args)\n\tcdNodeElse.AddArg(carg)\n\n\telseBlock.Push(cdNodeElse)\n\telseTree.Root = elseBlock\n\n\tifDecl.SetElseTree(elseTree)\n\n\tfnBlock.Push(ifDecl)\n\tfnTree.Root = fnBlock\n\tfn.SetTree(fnTree)\n\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"issue 22\", `fn gocd(path) {\n\tif $path == \"\" {\n\t\tcd $GOPATH\n\t} else {\n\t\tcd $GOPATH+\"/src/\"+$path\n\t}\n}`, expected, t, true)\n\n}\n\nfunc TestParseIssue38(t *testing.T) {\n\texpected := ast.NewTree(\"parse issue38\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tfnInv := ast.NewFnInvNode(token.NewFileInfo(1, 0), \"cd\")\n\targs := make([]ast.Expr, 3)\n\targs[0] = ast.NewVarExpr(token.NewFileInfo(1, 3), \"$GOPATH\")\n\targs[1] = ast.NewStringExpr(token.NewFileInfo(1, 12), \"/src/\", true)\n\targs[2] = ast.NewVarExpr(token.NewFileInfo(1, 19), \"$path\")\n\n\targ := ast.NewConcatExpr(token.NewFileInfo(1, 3), args)\n\tfnInv.AddArg(arg)\n\tln.Push(fnInv)\n\texpected.Root = ln\n\n\tparserTest(\"parse issue38\", `cd($GOPATH+\"/src/\"+$path)`, expected, t, true)\n}\n\nfunc TestParseIssue43(t *testing.T) {\n\tcontent := `fn gpull() {\n\tbranch <= git rev-parse --abbrev-ref HEAD | xargs echo -n\n\n\tgit pull origin $branch\n\n\trefreshPrompt()\n}`\n\n\texpected := ast.NewTree(\"parse issue 41\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tfnDecl := ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"gpull\")\n\tfnTree := ast.NewTree(\"fn\")\n\tfnBlock := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tgitRevParse := ast.NewCommandNode(token.NewFileInfo(2, 11), \"git\", false)\n\n\tgitRevParse.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 15), \"rev-parse\", true))\n\tgitRevParse.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 25), \"--abbrev-ref\", false))\n\tgitRevParse.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 38), \"HEAD\", false))\n\n\tbranchAssign, err := ast.NewExecAssignNode(token.NewFileInfo(2, 1), []*ast.NameNode{\n\t\tast.NewNameNode(token.NewFileInfo(2, 1),\n\t\t\t\"branch\",\n\t\t\tnil,\n\t\t)}, gitRevParse)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\txargs := ast.NewCommandNode(token.NewFileInfo(2, 45), \"xargs\", false)\n\txargs.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 51), \"echo\", false))\n\txargs.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 56), \"-n\", false))\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(2, 43), false)\n\tpipe.AddCmd(gitRevParse)\n\tpipe.AddCmd(xargs)\n\n\tbranchAssign.SetCommand(pipe)\n\n\tfnBlock.Push(branchAssign)\n\n\tgitPull := ast.NewCommandNode(token.NewFileInfo(1, 0), \"git\", false)\n\n\tgitPull.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 0), \"pull\", false))\n\tgitPull.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 0), \"origin\", false))\n\tgitPull.AddArg(ast.NewVarExpr(token.NewFileInfo(1, 0), \"$branch\"))\n\n\tfnBlock.Push(gitPull)\n\n\tfnInv := ast.NewFnInvNode(token.NewFileInfo(1, 0), \"refreshPrompt\")\n\tfnBlock.Push(fnInv)\n\tfnTree.Root = fnBlock\n\n\tfnDecl.SetTree(fnTree)\n\tln.Push(fnDecl)\n\n\texpected.Root = ln\n\n\tparserTest(\"parse issue 41\", content, expected, t, true)\n}\n\nfunc TestParseIssue68(t *testing.T) {\n\texpected := ast.NewTree(\"parse issue #68\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcatCmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cat\", false)\n\n\tcatArg := ast.NewStringExpr(token.NewFileInfo(1, 4), \"PKGBUILD\", false)\n\tcatCmd.AddArg(catArg)\n\n\tsedCmd := ast.NewCommandNode(token.NewFileInfo(1, 15), \"sed\", false)\n\tsedArg := ast.NewStringExpr(token.NewFileInfo(1, 20), `s#\\$pkgdir#/home/i4k/alt#g`, true)\n\tsedCmd.AddArg(sedArg)\n\n\tsedRedir := ast.NewRedirectNode(token.NewFileInfo(1, 49))\n\tsedRedirArg := ast.NewStringExpr(token.NewFileInfo(1, 51), \"PKGBUILD2\", false)\n\tsedRedir.SetLocation(sedRedirArg)\n\tsedCmd.AddRedirect(sedRedir)\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(1, 13), false)\n\tpipe.AddCmd(catCmd)\n\tpipe.AddCmd(sedCmd)\n\n\tln.Push(pipe)\n\texpected.Root = ln\n\n\tparserTest(\"parse issue #68\", `cat PKGBUILD | sed \"s#\\\\$pkgdir#/home/i4k/alt#g\" > PKGBUILD2`, expected, t, false)\n}\n\nfunc TestParseIssue69(t *testing.T) {\n\texpected := ast.NewTree(\"parse-issue-69\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tparts := make([]ast.Expr, 2)\n\n\tparts[0] = ast.NewVarExpr(token.NewFileInfo(1, 5), \"$a\")\n\tparts[1] = ast.NewStringExpr(token.NewFileInfo(1, 9), \"b\", true)\n\n\tconcat := ast.NewConcatExpr(token.NewFileInfo(1, 5), parts)\n\n\tlistValues := make([]ast.Expr, 1)\n\tlistValues[0] = concat\n\n\tlist := ast.NewListExpr(token.NewFileInfo(1, 4), listValues)\n\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"a\", nil), list,\n\t)\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"parse-issue-69\", `a = ($a+\"b\")`, expected, t, true)\n}\n\nfunc TestParseImportIssue94(t *testing.T) {\n\texpected := ast.NewTree(\"test import\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\timportStmt := ast.NewImportNode(token.NewFileInfo(1, 0), ast.NewStringExpr(token.NewFileInfo(1, 7), \"common\", false))\n\tln.Push(importStmt)\n\texpected.Root = ln\n\n\tparserTest(\"test import\", \"import common\", expected, t, true)\n}\n\nfunc TestParseIssue108(t *testing.T) {\n\t// keywords cannot be used as command arguments\n\n\texpected := ast.NewTree(\"parse issue #108\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcatCmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cat\", false)\n\n\tcatArg := ast.NewStringExpr(token.NewFileInfo(1, 4), \"spec.ebnf\", false)\n\tcatCmd.AddArg(catArg)\n\n\tgrepCmd := ast.NewCommandNode(token.NewFileInfo(1, 16), \"grep\", false)\n\tgrepArg := ast.NewStringExpr(token.NewFileInfo(1, 21), `-i`, false)\n\tgrepArg2 := ast.NewStringExpr(token.NewFileInfo(1, 24), \"rfork\", false)\n\n\tgrepCmd.AddArg(grepArg)\n\tgrepCmd.AddArg(grepArg2)\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(1, 14), false)\n\tpipe.AddCmd(catCmd)\n\tpipe.AddCmd(grepCmd)\n\n\tln.Push(pipe)\n\texpected.Root = ln\n\n\tparserTest(\"parse issue #108\", `cat spec.ebnf | grep -i rfork`, expected, t, false)\n}\n\nfunc TestParseIssue123(t *testing.T) {\n\tparser := NewParser(\"invalid cmd assignment\", `IFS <= (\"\\n\")`)\n\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Errorf(\"Must fail...\")\n\t\treturn\n\t}\n\n\texpected := \"invalid cmd assignment:1:9: Unexpected token STRING. Expecting IDENT or ARG\"\n\tif err.Error() != expected {\n\t\tt.Fatalf(\"Error string differs. Expecting '%s' but got '%s'\",\n\t\t\texpected, err.Error())\n\t}\n}\n"
  },
  {
    "path": "parser/parse_test.go",
    "content": "package parser\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/ast\"\n\t\"github.com/madlambda/nash/token\"\n)\n\nfunc parserTest(name, content string, expected *ast.Tree, t *testing.T, enableReverse bool) *ast.Tree {\n\tparser := NewParser(name, content)\n\ttr, err := parser.Parse()\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\tt.Logf(\"Failed syntax: '%s'\", content)\n\t\treturn nil\n\t}\n\n\tif tr == nil {\n\t\tt.Errorf(\"Failed to parse\")\n\t\treturn nil\n\t}\n\n\tif !expected.IsEqual(tr) {\n\t\tt.Errorf(\"Expected: %s\\n\\nResult: %s\\n\", expected, tr)\n\t\tt.Logf(\"Failed syntax: '%s'\", content)\n\t\treturn tr\n\t}\n\n\tif !enableReverse {\n\t\treturn tr\n\t}\n\n\t// Test if the reverse of tree is the content again... *hard*\n\ttrcontent := strings.TrimSpace(tr.String())\n\tcontent = strings.TrimSpace(content)\n\n\tif content != trcontent {\n\t\tt.Errorf(`Failed to reverse the tree.\nExpected:\n'%s'\n\nBut got:\n'%s'\n`, content, trcontent)\n\t}\n\n\treturn tr\n}\n\nfunc parserTestFail(t *testing.T, execStr string) {\n\tparser := NewParser(\"\", execStr)\n\n\ttr, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Errorf(\"Parsing '%s' must fail\", execStr)\n\t\treturn\n\t}\n\n\tif tr != nil {\n\t\tt.Error(\"tr must be nil\")\n\t\treturn\n\t}\n}\n\nfunc TestParseSimple(t *testing.T) {\n\texpected := ast.NewTree(\"parser simple\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"echo\", false)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 6), \"hello world\", true))\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `echo \"hello world\"`, expected, t, true)\n\n\tcmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cat\", false)\n\targ1 := ast.NewStringExpr(token.NewFileInfo(1, 4), \"/etc/resolv.conf\", false)\n\targ2 := ast.NewStringExpr(token.NewFileInfo(1, 21), \"/etc/hosts\", false)\n\tcmd1.AddArg(arg1)\n\tcmd1.AddArg(arg2)\n\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tln.Push(cmd1)\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `cat /etc/resolv.conf /etc/hosts`, expected, t, true)\n}\n\nfunc TestParseReverseGetSame(t *testing.T) {\n\tparser := NewParser(\"reverse simple\", \"echo \\\"hello world\\\"\")\n\n\ttr, err := parser.Parse()\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tif tr.String() != \"echo \\\"hello world\\\"\" {\n\t\tt.Errorf(\"Failed to reverse tree: %s\", tr.String())\n\t\treturn\n\t}\n}\n\nfunc TestParsePipe(t *testing.T) {\n\texpected := ast.NewTree(\"parser pipe\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tfirst := ast.NewCommandNode(token.NewFileInfo(1, 0), \"echo\", false)\n\tfirst.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 6), \"hello world\", true))\n\n\tsecond := ast.NewCommandNode(token.NewFileInfo(1, 21), \"awk\", false)\n\tsecond.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 26), \"{print $1}\", true))\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(1, 19), false)\n\tpipe.AddCmd(first)\n\tpipe.AddCmd(second)\n\n\tln.Push(pipe)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser pipe\", `echo \"hello world\" | awk \"{print $1}\"`, expected, t, true)\n}\n\nfunc TestBasicSetEnvAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"simple set assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tset, err := ast.NewSetenvNode(token.NewFileInfo(1, 0), \"test\", nil)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tln.Push(set)\n\texpected.Root = ln\n\n\tparserTest(\"simple set assignment\", `setenv test`, expected, t, true)\n\n\t// setenv with assignment\n\texpected = ast.NewTree(\"setenv with simple assignment\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 7),\n\t\tast.NewNameNode(token.NewFileInfo(1, 7), \"test\", nil),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 15), \"hello\", true))\n\tset, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), \"test\", assign)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tln.Push(set)\n\texpected.Root = ln\n\n\tparserTest(\"setenv with simple assignment\", `setenv test = \"hello\"`, expected, t, true)\n\n\texpected = ast.NewTree(\"setenv with simple cmd assignment\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 15), \"ls\", false)\n\n\tcmdAssign, err := ast.NewExecAssignNode(\n\t\ttoken.NewFileInfo(1, 7),\n\t\t[]*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 7), \"test\", nil)},\n\t\tcmd,\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tset, err = ast.NewSetenvNode(token.NewFileInfo(1, 0), \"test\", cmdAssign)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tln.Push(set)\n\texpected.Root = ln\n\n\tparserTest(\"simple assignment\", `setenv test <= ls`, expected, t, true)\n}\n\nfunc TestBasicAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"simple assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 8), \"hello\", true))\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"simple assignment\", `test = \"hello\"`, expected, t, true)\n\n\t// test concatenation of strings and variables\n\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tconcats := make([]ast.Expr, 2, 2)\n\tconcats[0] = ast.NewStringExpr(token.NewFileInfo(1, 8), \"hello\", true)\n\tconcats[1] = ast.NewVarExpr(token.NewFileInfo(1, 15), \"$var\")\n\n\targ1 := ast.NewConcatExpr(token.NewFileInfo(1, 8), concats)\n\n\tassign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\targ1,\n\t)\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"test\", `test = \"hello\"+$var`, expected, t, true)\n\n\tfor _, test := range []string{\n\t\t\"test=hello\",\n\t\t\"test = hello\",\n\t\t\"test = 1\",\n\t\t\"test = false\",\n\t\t\"test = -1\",\n\t\t`test = \"1\", \"2\"`,\n\t} {\n\t\tparserTestFail(t, test)\n\t}\n}\n\nfunc TestVarAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"var assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tvarAssign := ast.NewVarAssignDecl(token.NewFileInfo(1, 0),\n\t\tast.NewSingleAssignNode(token.NewFileInfo(1, 4),\n\t\t\tast.NewNameNode(token.NewFileInfo(1, 4), \"test\", nil),\n\t\t\tast.NewStringExpr(token.NewFileInfo(1, 12), \"hello\", true)),\n\t)\n\tln.Push(varAssign)\n\texpected.Root = ln\n\n\tparserTest(\"var assignment\", `var test = \"hello\"`, expected, t, true)\n\n\tfor _, test := range []string{\n\t\t\"var test=hello\",\n\t\t\"var\",\n\t\t\"var test\",\n\t\t\"var test = false\",\n\t\t\"var test = -1\",\n\t\t`var test = \"1\", \"2\"`,\n\t} {\n\t\tparserTestFail(t, test)\n\t}\n}\n\nfunc TestParseMultipleAssign(t *testing.T) {\n\tone := ast.NewNameNode(token.NewFileInfo(1, 0), \"one\", nil)\n\ttwo := ast.NewNameNode(token.NewFileInfo(1, 5), \"two\", nil)\n\tvalue1 := ast.NewStringExpr(token.NewFileInfo(1, 12), \"1\", true)\n\tvalue2 := ast.NewStringExpr(token.NewFileInfo(1, 17), \"2\", true)\n\tassign := ast.NewAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{one, two},\n\t\t[]ast.Expr{value1, value2},\n\t)\n\n\texpected := ast.NewTree(\"tuple assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"tuple assignment\", `one, two = \"1\", \"2\"`, expected, t, true)\n\n\tlvalue1 := ast.NewListExpr(token.NewFileInfo(1, 11), []ast.Expr{})\n\tvalue2 = ast.NewStringExpr(token.NewFileInfo(1, 16), \"2\", true)\n\tassign = ast.NewAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{one, two},\n\t\t[]ast.Expr{lvalue1, value2},\n\t)\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tln.Push(assign)\n\texpected.Root = ln\n\tparserTest(\"tuple assignment\", `one, two = (), \"2\"`, expected, t, true)\n\n}\n\nfunc TestParseMultipleExecAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"multiple cmd assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 16), \"ls\", false)\n\n\tassign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{\n\t\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\t\tast.NewNameNode(token.NewFileInfo(1, 6), \"status\", nil),\n\t\t},\n\t\tcmd,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"multiple cmd assignment\", `test, status <= ls`, expected, t, true)\n\n\texpected = ast.NewTree(\"multiple cmd assignment\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcmd = ast.NewCommandNode(token.NewFileInfo(1, 19), \"ls\", false)\n\n\tassign, err = ast.NewExecAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{\n\t\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", ast.NewIntExpr(token.NewFileInfo(1, 5), 0)),\n\t\t\tast.NewNameNode(token.NewFileInfo(1, 9), \"status\", nil),\n\t\t},\n\t\tcmd,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"multiple cmd assignment\", `test[0], status <= ls`, expected, t, true)\n}\n\nfunc TestParseInvalidIndexing(t *testing.T) {\n\t// test indexed assignment\n\tparser := NewParser(\"invalid\", `test[a] = \"a\"`)\n\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Parse must fail\")\n\t\treturn\n\t} else if err.Error() != \"invalid:1:5: Expected number or variable in index. Found ARG\" {\n\t\tt.Error(\"Invalid err msg\")\n\t\treturn\n\t}\n\n\tparser = NewParser(\"invalid\", `test[] = \"a\"`)\n\n\t_, err = parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Parse must fail\")\n\t\treturn\n\t} else if err.Error() != \"invalid:1:5: Expected number or variable in index. Found ]\" {\n\t\tt.Error(\"Invalid err msg\")\n\t\treturn\n\t}\n\n\tparser = NewParser(\"invalid\", `test[10.0] = \"a\"`)\n\n\t_, err = parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Parse must fail\")\n\t\treturn\n\t} else if err.Error() != \"invalid:1:5: Expected number or variable in index. Found ARG\" {\n\t\tt.Error(\"Invalid err msg\")\n\t\treturn\n\t}\n}\n\nfunc TestParseListAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"list assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tvalues := make([]ast.Expr, 0, 4)\n\n\tvalues = append(values,\n\t\tast.NewStringExpr(token.NewFileInfo(2, 1), \"plan9\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(3, 1), \"from\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(4, 1), \"bell\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(5, 1), \"labs\", false),\n\t)\n\n\telem := ast.NewListExpr(token.NewFileInfo(1, 7), values)\n\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\telem,\n\t)\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"list assignment\", `test = (\n\tplan9\n\tfrom\n\tbell\n\tlabs\n)`, expected, t, false)\n}\n\nfunc TestParseListOfListsAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"list assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tplan9 := make([]ast.Expr, 0, 4)\n\tplan9 = append(plan9,\n\t\tast.NewStringExpr(token.NewFileInfo(2, 2), \"plan9\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(2, 8), \"from\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(2, 13), \"bell\", false),\n\t\tast.NewStringExpr(token.NewFileInfo(2, 18), \"labs\", false),\n\t)\n\n\telem1 := ast.NewListExpr(token.NewFileInfo(2, 1), plan9)\n\n\tlinux := make([]ast.Expr, 0, 2)\n\tlinux = append(linux, ast.NewStringExpr(token.NewFileInfo(3, 2), \"linux\", false))\n\tlinux = append(linux, ast.NewStringExpr(token.NewFileInfo(3, 8), \"kernel\", false))\n\n\telem2 := ast.NewListExpr(token.NewFileInfo(3, 1), linux)\n\n\tvalues := make([]ast.Expr, 2)\n\tvalues[0] = elem1\n\tvalues[1] = elem2\n\n\telem := ast.NewListExpr(token.NewFileInfo(1, 7), values)\n\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\telem,\n\t)\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"list assignment\", `test = (\n\t(plan9 from bell labs)\n\t(linux kernel)\n\t)`, expected, t, false)\n}\n\nfunc TestParseCmdAssignment(t *testing.T) {\n\texpected := ast.NewTree(\"simple cmd assignment\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 8), \"ls\", false)\n\n\tassign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil)},\n\t\tcmd,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tln.Push(assign)\n\texpected.Root = ln\n\n\tparserTest(\"simple assignment\", `test <= ls`, expected, t, true)\n}\n\nfunc TestParseInvalidEmpty(t *testing.T) {\n\tparser := NewParser(\"invalid\", \";\")\n\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Parse must fail\")\n\t\treturn\n\t}\n}\n\nfunc TestParsePathCommand(t *testing.T) {\n\texpected := ast.NewTree(\"parser simple\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"/bin/echo\", false)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 11), \"hello world\", true))\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `/bin/echo \"hello world\"`, expected, t, true)\n}\n\nfunc TestParseWithShebang(t *testing.T) {\n\texpected := ast.NewTree(\"parser shebang\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmt := ast.NewCommentNode(token.NewFileInfo(1, 0), \"#!/bin/nash\")\n\tcmd := ast.NewCommandNode(token.NewFileInfo(3, 0), \"echo\", false)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 5), \"bleh\", false))\n\tln.Push(cmt)\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser shebang\", `#!/bin/nash\n\necho bleh\n`, expected, t, true)\n}\n\nfunc TestParseEmptyFile(t *testing.T) {\n\texpected := ast.NewTree(\"empty file\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\texpected.Root = ln\n\n\tparserTest(\"empty file\", \"\", expected, t, true)\n}\n\nfunc TestParseSingleCommand(t *testing.T) {\n\texpected := ast.NewTree(\"single command\")\n\texpected.Root = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\texpected.Root.Push(ast.NewCommandNode(token.NewFileInfo(1, 0), \"bleh\", false))\n\n\tparserTest(\"single command\", `bleh`, expected, t, true)\n}\n\nfunc TestParseRedirectSimple(t *testing.T) {\n\texpected := ast.NewTree(\"redirect\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cmd\", false)\n\tredir := ast.NewRedirectNode(token.NewFileInfo(1, 4))\n\tredir.SetMap(2, ast.RedirMapSupress)\n\tcmd.AddRedirect(redir)\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"simple redirect\", `cmd >[2=]`, expected, t, true)\n\n\texpected = ast.NewTree(\"redirect2\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(1, 0), \"cmd\", false)\n\tredir = ast.NewRedirectNode(token.NewFileInfo(1, 4))\n\tredir.SetMap(2, 1)\n\tcmd.AddRedirect(redir)\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"simple redirect\", `cmd >[2=1]`, expected, t, true)\n}\n\nfunc TestParseRedirectWithLocation(t *testing.T) {\n\texpected := ast.NewTree(\"redirect with location\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cmd\", false)\n\tredir := ast.NewRedirectNode(token.NewFileInfo(1, 4))\n\tredir.SetMap(2, ast.RedirMapNoValue)\n\tredir.SetLocation(ast.NewStringExpr(token.NewFileInfo(1, 9), \"/var/log/service.log\", false))\n\tcmd.AddRedirect(redir)\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"simple redirect\", `cmd >[2] /var/log/service.log`, expected, t, true)\n}\n\nfunc TestParseRedirectMultiples(t *testing.T) {\n\texpected := ast.NewTree(\"redirect multiples\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cmd\", false)\n\tredir1 := ast.NewRedirectNode(token.NewFileInfo(1, 4))\n\tredir2 := ast.NewRedirectNode(token.NewFileInfo(1, 11))\n\n\tredir1.SetMap(1, 2)\n\tredir2.SetMap(2, ast.RedirMapSupress)\n\n\tcmd.AddRedirect(redir1)\n\tcmd.AddRedirect(redir2)\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"multiple redirects\", `cmd >[1=2] >[2=]`, expected, t, true)\n}\n\nfunc TestParseCommandWithStringsEqualsNot(t *testing.T) {\n\texpected := ast.NewTree(\"strings works as expected\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), \"echo\", false)\n\tcmd2 := ast.NewCommandNode(token.NewFileInfo(2, 0), \"echo\", false)\n\tcmd1.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 5), \"hello\", false))\n\tcmd2.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 6), \"hello\", true))\n\n\tln.Push(cmd1)\n\tln.Push(cmd2)\n\texpected.Root = ln\n\n\tparserTest(\"strings works as expected\", `echo hello\necho \"hello\"\n`, expected, t, true)\n}\n\nfunc TestParseCommandSeparatedBySemicolon(t *testing.T) {\n\texpected := ast.NewTree(\"semicolon\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd1 := ast.NewCommandNode(token.NewFileInfo(1, 0), \"echo\", false)\n\tcmd2 := ast.NewCommandNode(token.NewFileInfo(1, 11), \"echo\", false)\n\tcmd1.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 5), \"hello\", false))\n\tcmd2.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), \"world\", false))\n\n\tln.Push(cmd1)\n\tln.Push(cmd2)\n\texpected.Root = ln\n\n\tparserTest(\"strings works as expected\", `echo hello;echo world`, expected, t, false)\n}\n\nfunc TestParseStringNotFinished(t *testing.T) {\n\tparser := NewParser(\"string not finished\", `echo \"hello world`)\n\ttr, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Error: should fail\")\n\t\treturn\n\t}\n\n\tif tr != nil {\n\t\tt.Errorf(\"Failed to parse\")\n\t\treturn\n\t}\n}\n\nfunc TestParseCd(t *testing.T) {\n\texpected := ast.NewTree(\"test cd\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cd\", false)\n\targ := ast.NewStringExpr(token.NewFileInfo(1, 3), \"/tmp\", false)\n\tcd.AddArg(arg)\n\tln.Push(cd)\n\texpected.Root = ln\n\n\tparserTest(\"test cd\", \"cd /tmp\", expected, t, true)\n\n\t// test cd into home\n\texpected = ast.NewTree(\"test cd into home\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcd = ast.NewCommandNode(token.NewFileInfo(1, 0), \"cd\", false)\n\tln.Push(cd)\n\texpected.Root = ln\n\n\tparserTest(\"test cd into home\", \"cd\", expected, t, true)\n\n\t// test cd ..\n\texpected = ast.NewTree(\"test cd ..\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcd = ast.NewCommandNode(token.NewFileInfo(1, 0), \"cd\", false)\n\tcd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 3), \"..\", false))\n\tln.Push(cd)\n\texpected.Root = ln\n\n\tparserTest(\"test cd ..\", \"cd ..\", expected, t, true)\n\n\texpected = ast.NewTree(\"cd into HOME by setenv\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tassign := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"HOME\", nil),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 8), \"/\", true),\n\t)\n\n\tset, err := ast.NewSetenvNode(token.NewFileInfo(3, 0), \"HOME\", nil)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcd = ast.NewCommandNode(token.NewFileInfo(5, 0), \"cd\", false)\n\tpwd := ast.NewCommandNode(token.NewFileInfo(6, 0), \"pwd\", false)\n\n\tln.Push(assign)\n\tln.Push(set)\n\tln.Push(cd)\n\tln.Push(pwd)\n\n\texpected.Root = ln\n\n\tparserTest(\"test cd into HOME by setenv\", `HOME = \"/\"\n\nsetenv HOME\n\ncd\npwd`, expected, t, true)\n\n\t// Test cd into custom variable\n\texpected = ast.NewTree(\"cd into variable value\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\targ = ast.NewStringExpr(token.NewFileInfo(1, 10), \"/home/i4k/gopath\", true)\n\n\tassign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"GOPATH\", nil),\n\t\targ,\n\t)\n\n\tcd = ast.NewCommandNode(token.NewFileInfo(3, 0), \"cd\", false)\n\targ2 := ast.NewVarExpr(token.NewFileInfo(3, 3), \"$GOPATH\")\n\tcd.AddArg(arg2)\n\n\tln.Push(assign)\n\tln.Push(cd)\n\n\texpected.Root = ln\n\n\tparserTest(\"test cd into variable value\", `GOPATH = \"/home/i4k/gopath\"\n\ncd $GOPATH`, expected, t, true)\n\n\t// Test cd into custom variable\n\texpected = ast.NewTree(\"cd into variable value with concat\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\targ = ast.NewStringExpr(token.NewFileInfo(1, 10), \"/home/i4k/gopath\", true)\n\n\tassign = ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"GOPATH\", nil),\n\t\targ,\n\t)\n\n\tconcat := make([]ast.Expr, 0, 2)\n\tconcat = append(concat, ast.NewVarExpr(token.NewFileInfo(3, 3), \"$GOPATH\"))\n\tconcat = append(concat, ast.NewStringExpr(token.NewFileInfo(3, 12), \"/src/github.com\", true))\n\n\tcd = ast.NewCommandNode(token.NewFileInfo(3, 0), \"cd\", false)\n\tcarg := ast.NewConcatExpr(token.NewFileInfo(3, 3), concat)\n\tcd.AddArg(carg)\n\n\tln.Push(assign)\n\tln.Push(cd)\n\n\texpected.Root = ln\n\n\tparserTest(\"test cd into variable value\", `GOPATH = \"/home/i4k/gopath\"\n\ncd $GOPATH+\"/src/github.com\"`, expected, t, true)\n\n}\n\nfunc TestParseConcatOfIndexedVar(t *testing.T) {\n\texpected := ast.NewTree(\"concat indexed var\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\targ1 := ast.NewStringExpr(token.NewFileInfo(1, 4), \"ec2\", false)\n\targ2 := ast.NewStringExpr(token.NewFileInfo(1, 8), \"create-tags\", false)\n\targ3 := ast.NewStringExpr(token.NewFileInfo(1, 20), \"--resources\", false)\n\targ4 := ast.NewVarExpr(token.NewFileInfo(1, 32), \"$resource\")\n\targ5 := ast.NewStringExpr(token.NewFileInfo(1, 42), \"--tags\", false)\n\n\tc1 := ast.NewStringExpr(token.NewFileInfo(1, 50), \"Key=\", true)\n\tc2 := ast.NewIndexExpr(token.NewFileInfo(1, 56),\n\t\tast.NewVarExpr(token.NewFileInfo(1, 56), \"$tag\"),\n\t\tast.NewIntExpr(token.NewFileInfo(1, 61), 0))\n\tc3 := ast.NewStringExpr(token.NewFileInfo(1, 65), \",Value=\", true)\n\tc4 := ast.NewIndexExpr(token.NewFileInfo(1, 74),\n\t\tast.NewVarExpr(token.NewFileInfo(1, 74), \"$tag\"),\n\t\tast.NewIntExpr(token.NewFileInfo(1, 79), 1))\n\tcvalues := make([]ast.Expr, 4)\n\tcvalues[0] = c1\n\tcvalues[1] = c2\n\tcvalues[2] = c3\n\tcvalues[3] = c4\n\n\targ6 := ast.NewConcatExpr(token.NewFileInfo(1, 50), cvalues)\n\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"aws\", false)\n\tcmd.AddArg(arg1)\n\tcmd.AddArg(arg2)\n\tcmd.AddArg(arg3)\n\tcmd.AddArg(arg4)\n\tcmd.AddArg(arg5)\n\tcmd.AddArg(arg6)\n\n\tln.Push(cmd)\n\texpected.Root = ln\n\n\tparserTest(\"concat indexed var\",\n\t\t`aws ec2 create-tags --resources $resource --tags \"Key=\"+$tag[0]+\",Value=\"+$tag[1]`,\n\t\texpected, t, true)\n}\n\nfunc TestParseRfork(t *testing.T) {\n\texpected := ast.NewTree(\"test rfork\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd1 := ast.NewRforkNode(token.NewFileInfo(1, 0))\n\tf1 := ast.NewStringExpr(token.NewFileInfo(1, 6), \"u\", false)\n\tcmd1.SetFlags(f1)\n\tln.Push(cmd1)\n\texpected.Root = ln\n\n\tparserTest(\"test rfork\", \"rfork u\", expected, t, true)\n}\n\nfunc TestParseRforkWithBlock(t *testing.T) {\n\texpected := ast.NewTree(\"rfork with block\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\trfork := ast.NewRforkNode(token.NewFileInfo(1, 0))\n\targ := ast.NewStringExpr(token.NewFileInfo(1, 6), \"u\", false)\n\trfork.SetFlags(arg)\n\n\tinsideFork := ast.NewCommandNode(token.NewFileInfo(2, 1), \"mount\", false)\n\tinsideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 7), \"-t\", false))\n\tinsideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 10), \"proc\", false))\n\tinsideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 15), \"proc\", false))\n\tinsideFork.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 20), \"/proc\", false))\n\n\tbln := ast.NewBlockNode(token.NewFileInfo(1, 8))\n\tbln.Push(insideFork)\n\tsubtree := ast.NewTree(\"rfork\")\n\tsubtree.Root = bln\n\n\trfork.SetTree(subtree)\n\n\tln.Push(rfork)\n\texpected.Root = ln\n\n\tparserTest(\"rfork with block\", `rfork u {\n\tmount -t proc proc /proc\n}\n`, expected, t, true)\n\n}\n\nfunc TestUnpairedRforkBlocks(t *testing.T) {\n\tparser := NewParser(\"unpaired\", \"rfork u {\")\n\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Errorf(\"Should fail because of unpaired open/close blocks\")\n\t\treturn\n\t}\n}\n\nfunc TestParseImport(t *testing.T) {\n\texpected := ast.NewTree(\"test import\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\timportStmt := ast.NewImportNode(token.NewFileInfo(1, 0),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 7), \"env.sh\", false))\n\tln.Push(importStmt)\n\texpected.Root = ln\n\n\tparserTest(\"test import\", \"import env.sh\", expected, t, true)\n\n\texpected = ast.NewTree(\"test import with quotes\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\timportStmt = ast.NewImportNode(token.NewFileInfo(1, 0),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 8), \"env.sh\", true))\n\tln.Push(importStmt)\n\texpected.Root = ln\n\n\tparserTest(\"test import\", `import \"env.sh\"`, expected, t, true)\n}\n\nfunc TestParseIf(t *testing.T) {\n\texpected := ast.NewTree(\"test if\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewStringExpr(token.NewFileInfo(1, 4), \"test\", true))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 14), \"other\", true))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 21))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if \"test\" == \"other\" {\n\tpwd\n}`, expected, t, true)\n\n\texpected = ast.NewTree(\"test if\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl = ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewStringExpr(token.NewFileInfo(1, 4), \"\", true))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 10), \"other\", true))\n\tifDecl.SetOp(\"!=\")\n\n\tsubBlock = ast.NewBlockNode(token.NewFileInfo(1, 17))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree = ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if \"\" != \"other\" {\n\tpwd\n}`, expected, t, true)\n}\n\nfunc TestParseFuncall(t *testing.T) {\n\texpected := ast.NewTree(\"fn inv\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\taFn := ast.NewFnInvNode(token.NewFileInfo(1, 0), \"a\")\n\tln.Push(aFn)\n\texpected.Root = ln\n\n\tparserTest(\"test basic fn inv\", `a()`, expected, t, true)\n\n\texpected = ast.NewTree(\"fn inv\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\taFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), \"a\")\n\tbFn := ast.NewFnInvNode(token.NewFileInfo(1, 2), \"b\")\n\taFn.AddArg(bFn)\n\tln.Push(aFn)\n\texpected.Root = ln\n\n\tparserTest(\"test fn composition\", `a(b())`, expected, t, true)\n\n\texpected = ast.NewTree(\"fn inv\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\taFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), \"a\")\n\tbFn = ast.NewFnInvNode(token.NewFileInfo(1, 2), \"b\")\n\tb2Fn := ast.NewFnInvNode(token.NewFileInfo(1, 7), \"b\")\n\taFn.AddArg(bFn)\n\taFn.AddArg(b2Fn)\n\tln.Push(aFn)\n\texpected.Root = ln\n\n\tparserTest(\"test fn composition\", `a(b(), b())`, expected, t, true)\n\n\texpected = ast.NewTree(\"fn inv\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\taFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), \"a\")\n\tbFn = ast.NewFnInvNode(token.NewFileInfo(1, 2), \"b\")\n\tb2Fn = ast.NewFnInvNode(token.NewFileInfo(1, 4), \"b\")\n\tbFn.AddArg(b2Fn)\n\taFn.AddArg(bFn)\n\tln.Push(aFn)\n\texpected.Root = ln\n\n\tparserTest(\"test fn composition\", `a(b(b()))`, expected, t, true)\n\n\texpected = ast.NewTree(\"fn inv list\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\taFn = ast.NewFnInvNode(token.NewFileInfo(1, 0), \"a\")\n\tlExpr := ast.NewListExpr(token.NewFileInfo(1, 2), []ast.Expr{\n\t\tast.NewStringExpr(token.NewFileInfo(1, 4), \"1\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 8), \"2\", true),\n\t\tast.NewStringExpr(token.NewFileInfo(1, 12), \"3\", true),\n\t})\n\taFn.AddArg(lExpr)\n\tln.Push(aFn)\n\texpected.Root = ln\n\tparserTest(\"test fn list arg\", `a((\"1\" \"2\" \"3\"))`, expected, t, true)\n\n\t// test valid funcall syntaxes (do not verify AST)\n\tfor _, tc := range []string{\n\t\t`func()`,\n\t\t`func(())`, // empty list\n\t\t`func($a)`,\n\t\t`_($a, $b)`,\n\t\t`func($a())`,\n\t\t`func($a(), $b())`,\n\t\t`func($a($b($c())))`,\n\t\t`func($a(\"a\"))`,\n\t\t`__((((()))))`, // perfect fit for a nash obfuscating code contest\n\t\t`_(\n\t\t\t()\n\t\t)`,\n\t\t`_(\n\t\t(), (), (), (), (),\n\t\t)`,\n\t\t`_((() () () () ()))`,\n\t\t`deploy((bomb shell))`, // unquoted list elements are still supported :-(\n\t\t`func(\"a\", ())`,\n\t\t`_($a+$b)`,\n\t\t`_($a+\"\")`,\n\t\t`_(\"\"+$a)`,\n\t\t`func((()()))`,\n\t} {\n\t\tparser := NewParser(\"test\", tc)\n\t\t_, err := parser.Parse()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestParseFuncallInvalid(t *testing.T) {\n\tfor _, tc := range []string{\n\t\t`test(()`,\n\t\t`_())`,\n\t\t`func(a)`,\n\t\t`func(\"a\", a)`,\n\t\t`func(_(((((()))))`,\n\t\t`func(()+())`,\n\t\t`func(\"1\"+(\"2\" \"3\"))`,\n\t\t`func(()())`,\n\t} {\n\t\tparser := NewParser(\"test\", tc)\n\t\t_, err := parser.Parse()\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Syntax '%s' must fail...\", tc)\n\t\t}\n\t}\n}\n\nfunc TestParseIfFnInv(t *testing.T) {\n\texpected := ast.NewTree(\"test if\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewFnInvNode(token.NewFileInfo(1, 3), \"test\"))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 14), \"other\", true))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 21))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if test() == \"other\" {\n\tpwd\n}`, expected, t, true)\n\n\texpected = ast.NewTree(\"test if\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl = ast.NewIfNode(token.NewFileInfo(1, 0))\n\n\tfnInv := ast.NewFnInvNode(token.NewFileInfo(1, 3), \"test\")\n\tfnInv.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 9), \"bleh\", true))\n\tifDecl.SetLvalue(fnInv)\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 20), \"other\", true))\n\tifDecl.SetOp(\"!=\")\n\n\tsubBlock = ast.NewBlockNode(token.NewFileInfo(1, 27))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree = ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if test(\"bleh\") != \"other\" {\n\tpwd\n}`, expected, t, true)\n}\n\nfunc TestParseIfLvariable(t *testing.T) {\n\texpected := ast.NewTree(\"test if with variable\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), \"$test\"))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), \"other\", true))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 20))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if $test == \"other\" {\n\tpwd\n}`, expected, t, true)\n}\n\nfunc TestParseIfRvariable(t *testing.T) {\n\texpected := ast.NewTree(\"test if with variable\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), \"$test\"))\n\tifDecl.SetRvalue(ast.NewVarExpr(token.NewFileInfo(1, 12), \"$other\"))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 19))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if $test == $other {\n\tpwd\n}`, expected, t, true)\n}\n\nfunc TestParseIfElse(t *testing.T) {\n\texpected := ast.NewTree(\"test if else with variable\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), \"$test\"))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), \"other\", true))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 20))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\telseBlock := ast.NewBlockNode(token.NewFileInfo(3, 7))\n\texitCmd := ast.NewCommandNode(token.NewFileInfo(4, 1), \"exit\", false)\n\telseBlock.Push(exitCmd)\n\n\telseTree := ast.NewTree(\"else block\")\n\telseTree.Root = elseBlock\n\n\tifDecl.SetElseTree(elseTree)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if $test == \"other\" {\n\tpwd\n} else {\n\texit\n}`, expected, t, true)\n}\n\nfunc TestParseIfElseIf(t *testing.T) {\n\texpected := ast.NewTree(\"test if else with variable\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tifDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(1, 3), \"$test\"))\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 13), \"other\", true))\n\tifDecl.SetOp(\"==\")\n\n\tsubBlock := ast.NewBlockNode(token.NewFileInfo(1, 20))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(2, 1), \"pwd\", false)\n\tsubBlock.Push(cmd)\n\n\tifTree := ast.NewTree(\"if block\")\n\tifTree.Root = subBlock\n\n\tifDecl.SetIfTree(ifTree)\n\n\telseIfDecl := ast.NewIfNode(token.NewFileInfo(3, 7))\n\n\telseIfDecl.SetLvalue(ast.NewVarExpr(token.NewFileInfo(3, 10), \"$test\"))\n\telseIfDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(3, 20), \"others\", true))\n\telseIfDecl.SetOp(\"==\")\n\n\telseIfBlock := ast.NewBlockNode(token.NewFileInfo(3, 28))\n\telseifCmd := ast.NewCommandNode(token.NewFileInfo(4, 1), \"ls\", false)\n\telseIfBlock.Push(elseifCmd)\n\n\telseIfTree := ast.NewTree(\"if block\")\n\telseIfTree.Root = elseIfBlock\n\n\telseIfDecl.SetIfTree(elseIfTree)\n\n\telseBlock := ast.NewBlockNode(token.NewFileInfo(5, 7))\n\texitCmd := ast.NewCommandNode(token.NewFileInfo(6, 1), \"exit\", false)\n\telseBlock.Push(exitCmd)\n\n\telseTree := ast.NewTree(\"else block\")\n\telseTree.Root = elseBlock\n\n\telseIfDecl.SetElseTree(elseTree)\n\n\telseBlock2 := ast.NewBlockNode(token.NewFileInfo(3, 7))\n\telseBlock2.Push(elseIfDecl)\n\n\telseTree2 := ast.NewTree(\"first else tree\")\n\telseTree2.Root = elseBlock2\n\tifDecl.SetElseTree(elseTree2)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"test if\", `if $test == \"other\" {\n\tpwd\n} else if $test == \"others\" {\n\tls\n} else {\n\texit\n}`, expected, t, true)\n}\n\nfunc TestParseFnBasic(t *testing.T) {\n\t// root\n\texpected := ast.NewTree(\"fn\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\t// fn\n\tfn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"build\")\n\ttree := ast.NewTree(\"fn body\")\n\tlnBody := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"fn\", `fn build() {\n\n}`, expected, t, true)\n\n\t// root\n\texpected = ast.NewTree(\"fn\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\t// fn\n\tfn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"build\")\n\ttree = ast.NewTree(\"fn body\")\n\tlnBody = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"ls\", false)\n\tlnBody.Push(cmd)\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"fn\", `fn build() {\n\tls\n}`, expected, t, true)\n\n\t// root\n\texpected = ast.NewTree(\"fn\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\t// fn\n\tfn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"build\")\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 9), \"image\", false))\n\ttree = ast.NewTree(\"fn body\")\n\tlnBody = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(1, 0), \"ls\", false)\n\tlnBody.Push(cmd)\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"fn\", `fn build(image) {\n\tls\n}`, expected, t, true)\n\n\t// root\n\texpected = ast.NewTree(\"fn\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\t// fn\n\tfn = ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"build\")\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 9), \"image\", false))\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 16), \"debug\", false))\n\ttree = ast.NewTree(\"fn body\")\n\tlnBody = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(1, 0), \"ls\", false)\n\tlnBody.Push(cmd)\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"fn\", `fn build(image, debug) {\n\tls\n}`, expected, t, true)\n}\n\nfunc TestParseInlineFnDecl(t *testing.T) {\n\texpected := ast.NewTree(\"fn\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tfn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"cd\")\n\ttree := ast.NewTree(\"fn body\")\n\tlnBody := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\techo := ast.NewCommandNode(token.NewFileInfo(1, 11), \"echo\", false)\n\techo.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), \"hello\", true))\n\tlnBody.Push(echo)\n\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"inline fn\", `fn cd() { echo \"hello\" }`,\n\t\texpected, t, false)\n\n\ttest := ast.NewCommandNode(token.NewFileInfo(1, 26), \"test\", false)\n\ttest.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 32), \"-d\", false))\n\ttest.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 35), \"/etc\", false))\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(1, 11), false)\n\tpipe.AddCmd(echo)\n\tpipe.AddCmd(test)\n\tlnBody = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tlnBody.Push(pipe)\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"inline fn\", `fn cd() { echo \"hello\" | test -d /etc }`,\n\t\texpected, t, false)\n}\n\nfunc TestParseBindFn(t *testing.T) {\n\texpected := ast.NewTree(\"bindfn\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tbindFn := ast.NewBindFnNode(token.NewFileInfo(1, 0), \"cd\", \"cd2\")\n\tln.Push(bindFn)\n\texpected.Root = ln\n\n\tparserTest(\"bindfn\", `bindfn cd cd2`, expected, t, true)\n}\n\nfunc TestParseRedirectionVariable(t *testing.T) {\n\texpected := ast.NewTree(\"redirection var\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 0), \"cmd\", false)\n\tredir := ast.NewRedirectNode(token.NewFileInfo(1, 4))\n\tredirArg := ast.NewVarExpr(token.NewFileInfo(1, 6), \"$outFname\")\n\tredir.SetLocation(redirArg)\n\tcmd.AddRedirect(redir)\n\tln.Push(cmd)\n\texpected.Root = ln\n\n\tparserTest(\"redir var\", `cmd > $outFname`, expected, t, true)\n}\n\nfunc TestParseReturn(t *testing.T) {\n\texpected := ast.NewTree(\"return\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tret := ast.NewReturnNode(token.NewFileInfo(1, 0))\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return`, expected, t, true)\n\n\texpected = ast.NewTree(\"return list\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tret = ast.NewReturnNode(token.NewFileInfo(1, 0))\n\n\tlistvalues := make([]ast.Expr, 2)\n\n\tlistvalues[0] = ast.NewStringExpr(token.NewFileInfo(1, 9), \"val1\", true)\n\tlistvalues[1] = ast.NewStringExpr(token.NewFileInfo(1, 16), \"val2\", true)\n\n\tretReturn := ast.NewListExpr(token.NewFileInfo(1, 7), listvalues)\n\n\tret.Returns = []ast.Expr{retReturn}\n\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return (\"val1\" \"val2\")`, expected, t, true)\n\n\texpected = ast.NewTree(\"return variable\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tret = ast.NewReturnNode(token.NewFileInfo(1, 0))\n\n\tret.Returns = []ast.Expr{ast.NewVarExpr(token.NewFileInfo(1, 7), \"$var\")}\n\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return $var`, expected, t, true)\n\n\texpected = ast.NewTree(\"return string\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tret = ast.NewReturnNode(token.NewFileInfo(1, 0))\n\n\tret.Returns = []ast.Expr{ast.NewStringExpr(token.NewFileInfo(1, 8), \"value\", true)}\n\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return \"value\"`, expected, t, true)\n\n\texpected = ast.NewTree(\"return funcall\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tret = ast.NewReturnNode(token.NewFileInfo(1, 0))\n\n\taFn := ast.NewFnInvNode(token.NewFileInfo(1, 7), \"a\")\n\n\tret.Returns = []ast.Expr{aFn}\n\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return a()`, expected, t, true)\n\n\texpected = ast.NewTree(\"return multiple values\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tret = ast.NewReturnNode(token.NewFileInfo(1, 0))\n\n\ta1 := ast.NewStringExpr(token.NewFileInfo(1, 8), \"1\", true)\n\ta2 := ast.NewStringExpr(token.NewFileInfo(1, 13), \"2\", true)\n\ta3 := ast.NewStringExpr(token.NewFileInfo(1, 18), \"3\", true)\n\n\tret.Returns = []ast.Expr{a1, a2, a3}\n\n\tln.Push(ret)\n\texpected.Root = ln\n\n\tparserTest(\"return\", `return \"1\", \"2\", \"3\"`, expected, t, true)\n}\n\nfunc TestParseIfInvalid(t *testing.T) {\n\tparser := NewParser(\"if invalid\", `if a == b { pwd }`)\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Must fail. Only quoted strings and variables on if clauses.\")\n\t\treturn\n\t}\n}\n\nfunc TestParseFor(t *testing.T) {\n\texpected := ast.NewTree(\"for\")\n\n\tforStmt := ast.NewForNode(token.NewFileInfo(1, 0))\n\tforTree := ast.NewTree(\"for block\")\n\tforBlock := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tforTree.Root = forBlock\n\tforStmt.SetTree(forTree)\n\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tln.Push(forStmt)\n\texpected.Root = ln\n\n\tparserTest(\"for\", `for {\n\n}`, expected, t, true)\n\n\tforStmt.SetIdentifier(\"f\")\n\tforStmt.SetInExpr(ast.NewVarExpr(token.NewFileInfo(1, 9), \"$files\"))\n\n\tparserTest(\"for\", `for f in $files {\n\n}`, expected, t, true)\n\n\tforStmt.SetIdentifier(\"f\")\n\tfnInv := ast.NewFnInvNode(token.NewFileInfo(1, 9), \"getfiles\")\n\tfnArg := ast.NewStringExpr(token.NewFileInfo(1, 19), \"/\", true)\n\tfnInv.AddArg(fnArg)\n\tforStmt.SetInExpr(fnInv)\n\n\tparserTest(\"for\", `for f in getfiles(\"/\") {\n\n}`, expected, t, true)\n\n\tforStmt.SetIdentifier(\"f\")\n\tvalue1 := ast.NewStringExpr(token.NewFileInfo(1, 10), \"1\", false)\n\tvalue2 := ast.NewStringExpr(token.NewFileInfo(1, 12), \"2\", false)\n\tvalue3 := ast.NewStringExpr(token.NewFileInfo(1, 14), \"3\", false)\n\tvalue4 := ast.NewStringExpr(token.NewFileInfo(1, 16), \"4\", false)\n\tvalue5 := ast.NewStringExpr(token.NewFileInfo(1, 18), \"5\", false)\n\n\tlist := ast.NewListExpr(token.NewFileInfo(1, 9), []ast.Expr{\n\t\tvalue1, value2, value3, value4, value5,\n\t})\n\n\tforStmt.SetInExpr(list)\n\n\tparserTest(\"for\", `for f in (1 2 3 4 5) {\n\n}`, expected, t, true)\n}\n\nfunc TestParseVariableIndexing(t *testing.T) {\n\texpected := ast.NewTree(\"variable indexing\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tindexedVar := ast.NewIndexExpr(\n\t\ttoken.NewFileInfo(1, 7),\n\t\tast.NewVarExpr(token.NewFileInfo(1, 7), \"$values\"),\n\t\tast.NewIntExpr(token.NewFileInfo(1, 15), 0),\n\t)\n\n\tassignment := ast.NewSingleAssignNode(token.NewFileInfo(1, 0),\n\t\tast.NewNameNode(token.NewFileInfo(1, 0), \"test\", nil),\n\t\tindexedVar,\n\t)\n\n\tln.Push(assignment)\n\texpected.Root = ln\n\n\tparserTest(\"variable indexing\", `test = $values[0]`, expected, t, true)\n\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\tifDecl := ast.NewIfNode(token.NewFileInfo(1, 0))\n\tlvalue := ast.NewVarExpr(token.NewFileInfo(1, 3), \"$values\")\n\n\tindexedVar = ast.NewIndexExpr(token.NewFileInfo(1, 3), lvalue,\n\t\tast.NewIntExpr(token.NewFileInfo(1, 11), 0))\n\n\tifDecl.SetLvalue(indexedVar)\n\tifDecl.SetOp(\"==\")\n\tifDecl.SetRvalue(ast.NewStringExpr(token.NewFileInfo(1, 18), \"1\", true))\n\n\tifBlock := ast.NewTree(\"if\")\n\tlnBody := ast.NewBlockNode(token.NewFileInfo(1, 21))\n\tifBlock.Root = lnBody\n\tifDecl.SetIfTree(ifBlock)\n\n\tln.Push(ifDecl)\n\texpected.Root = ln\n\n\tparserTest(\"variable indexing\", `if $values[0] == \"1\" {\n\n}`, expected, t, true)\n}\n\nfunc TestParseMultilineCmdExec(t *testing.T) {\n\texpected := ast.NewTree(\"parser simple\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 1), \"echo\", true)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 7), \"hello world\", true))\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `(echo \"hello world\")`, expected, t, true)\n\n\texpected = ast.NewTree(\"parser aws cmd\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd = ast.NewCommandNode(token.NewFileInfo(2, 1), \"aws\", true)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 5), \"ec2\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 9), \"run-instances\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 3), \"--image-id\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 14), \"ami-xxxxxxxx\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(4, 3), \"--count\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(4, 11), \"1\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(5, 3), \"--instance-type\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(5, 19), \"t1.micro\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(6, 3), \"--key-name\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(6, 14), \"MyKeyPair\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(7, 3), \"--security-groups\", false))\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(7, 21), \"my-sg\", false))\n\n\tln.Push(cmd)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `(\n\taws ec2 run-instances\n\t\t\t--image-id ami-xxxxxxxx\n\t\t\t--count 1\n\t\t\t--instance-type t1.micro\n\t\t\t--key-name MyKeyPair\n\t\t\t--security-groups my-sg\n)`, expected, t, true)\n}\n\nfunc TestParseMultilineCmdAssign(t *testing.T) {\n\texpected := ast.NewTree(\"parser simple assign\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tcmd := ast.NewCommandNode(token.NewFileInfo(1, 10), \"echo\", true)\n\tcmd.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 16), \"hello world\", true))\n\tassign, err := ast.NewExecAssignNode(token.NewFileInfo(1, 0),\n\t\t[]*ast.NameNode{ast.NewNameNode(token.NewFileInfo(1, 0), \"hello\", nil)},\n\t\tcmd,\n\t)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tln.Push(assign)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser simple\", `hello <= (echo \"hello world\")`, expected, t, true)\n}\n\nfunc TestMultiPipe(t *testing.T) {\n\texpected := ast.NewTree(\"parser pipe\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tfirst := ast.NewCommandNode(token.NewFileInfo(1, 1), \"echo\", false)\n\tfirst.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 7), \"hello world\", true))\n\n\tsecond := ast.NewCommandNode(token.NewFileInfo(1, 22), \"awk\", false)\n\tsecond.AddArg(ast.NewStringExpr(token.NewFileInfo(1, 27), \"{print $1}\", true))\n\n\tpipe := ast.NewPipeNode(token.NewFileInfo(1, 20), true)\n\tpipe.AddCmd(first)\n\tpipe.AddCmd(second)\n\n\tln.Push(pipe)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser pipe\", `(echo \"hello world\" | awk \"{print $1}\")`, expected, t, true)\n\n\t// get longer stringify\n\texpected = ast.NewTree(\"parser pipe\")\n\tln = ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tfirst = ast.NewCommandNode(token.NewFileInfo(2, 1), \"echo\", false)\n\tfirst.AddArg(ast.NewStringExpr(token.NewFileInfo(2, 7), \"hello world\", true))\n\n\tsecond = ast.NewCommandNode(token.NewFileInfo(3, 1), \"awk\", false)\n\tsecond.AddArg(ast.NewStringExpr(token.NewFileInfo(3, 6), \"{print AAAAAAAAAAAAAAAAAAAAAA}\", true))\n\n\tpipe = ast.NewPipeNode(token.NewFileInfo(2, 20), true)\n\tpipe.AddCmd(first)\n\tpipe.AddCmd(second)\n\n\tln.Push(pipe)\n\n\texpected.Root = ln\n\n\tparserTest(\"parser pipe\", `(\n\techo \"hello world\" |\n\tawk \"{print AAAAAAAAAAAAAAAAAAAAAA}\"\n)`, expected, t, true)\n}\n\nfunc TestFnVariadic(t *testing.T) {\n\t// root\n\texpected := ast.NewTree(\"variadic\")\n\tln := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\n\t// fn\n\tfn := ast.NewFnDeclNode(token.NewFileInfo(1, 3), \"println\")\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 11), \"fmt\", false))\n\tfn.AddArg(ast.NewFnArgNode(token.NewFileInfo(1, 16), \"arg\", true))\n\ttree := ast.NewTree(\"fn body\")\n\tlnBody := ast.NewBlockNode(token.NewFileInfo(1, 0))\n\tprint := ast.NewFnInvNode(token.NewFileInfo(2, 2), \"print\")\n\tprint.AddArg(ast.NewConcatExpr(token.NewFileInfo(1, 7), []ast.Expr{\n\t\tast.NewVarExpr(token.NewFileInfo(2, 7), \"$fmt\"),\n\t\tast.NewStringExpr(token.NewFileInfo(2, 12), \"\\n\", true),\n\t}))\n\tprint.AddArg(ast.NewVarVariadicExpr(token.NewFileInfo(2, 12), \"$arg\", true))\n\tlnBody.Push(print)\n\ttree.Root = lnBody\n\tfn.SetTree(tree)\n\n\t// root\n\tln.Push(fn)\n\texpected.Root = ln\n\n\tparserTest(\"fn\", `fn println(fmt, arg...) {\n\tprint($fmt+\"\\n\", $arg...)\n}`, expected, t, true)\n}\n\nfunc TestParseValidDotdotdot(t *testing.T) {\n\tfor _, tc := range []string{\n\t\t// things that should not break\n\t\t\"ls ...\",\n\t\t\"go get ./...\",\n\t\t`echo \"...\"`,\n\t\t`strangecmd... -h`,\n\t\t`bad_designed...fail -f`,\n\t} {\n\t\tparser := NewParser(\"\", tc)\n\t\t_, err := parser.Parse()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Code: '%s' failed: %s\", tc, err.Error())\n\t\t}\n\t}\n}\n\nfunc TestParseInvalidDotdotdot(t *testing.T) {\n\tfor _, tc := range []string{\n\t\t\"...\",\n\t\t`if ... == \"\" {}`,\n\t\t`if $var... == \"\" {}`,\n\t\t`a = $var...`,\n\t\t`a, b, c = (\"a\" \"b\" \"c\")...`, // please, no\n\t\t// `fn println(arg..., fmt) {}`, // Not sure if must fail at parsing...\n\t} {\n\t\tparser := NewParser(\"\", tc)\n\t\t_, err := parser.Parse()\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Syntax '%s' must fail\", tc)\n\t\t}\n\t}\n}\n\nfunc TestFunctionPipes(t *testing.T) {\n\tparser := NewParser(\"invalid pipe with functions\",\n\t\t`echo \"some thing\" | replace(\" \", \"|\")`)\n\n\t_, err := parser.Parse()\n\n\tif err == nil {\n\t\tt.Error(\"Must fail. Function must be bind'ed to command name to use in pipe.\")\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "proposal/1-scope-management.md",
    "content": "# Proposal: Proper scope management\n\nThis has already been implemented but these docs remain here as some sort\nof rationale. In the end we implemented proposal one, requiring new variables\nto be declared with the **var** keyword.\n\n## Abstract\n\nCurrently on nash there is no way to properly work\nwith closures because scope management is very limited.\n\nLets elaborate on the problem by implementing a\nlist object by instantiating a set of functions\nthat manipulates the same data.\n\n```sh\nfn list() {\n\tl = ()\n\n\tfn add(val) {\n\t\tl <= append($l, $val)\n\t}\n\n\tfn get(i) {\n\t\treturn $l[$i]\n\t}\n\n\tfn string() {\n\t\tprint(\"list: [%s]\\n\", $l)\n\t}\n\n\treturn $add, $get, $string\n}\n```\n\nThe idea is to hide all list data behind these 3 functions\nthat will manipulate the same data. The problem is that today\nthis is not possible, using this code:\n\n```sh\nadd, get, string <= list()\n\n$add(\"1\")\n$add(\"2\")\n$string()\n\nv <= $get(\"0\")\necho $v\n```\n\nWill result in:\n\n```\nlist: []\n/tmp/test.sh:27:5: /tmp/test.sh:11:23: Index out of bounds. len($l) == 0, but given 0\n```\n\nAs you can see, even when we call the **add** function the list\nremains empty, why is that ? The problem is on the add function:\n\n```sh\nfn add(val) {\n\tl <= append($l, $val)\n}\n```\n\nWhen we reference the **l** variable it uses the reference on the\nouter scope (the empty list), but there is no way to express syntactically\nthat we want to change the list on the outer scope instead of creating\na new variable **l** (shadowing the outer **l**).\n\nThat is why the **get** and **print** functions\nare always referencing an outer list **l** that is empty, a new one\nis created each time the add function is called.\n\nIn this document we navigate the solution space for this problem.\n\n## Proposal I - Create new variables explicitly\n\nOn this proposal new variable creation requires an explicit\nsyntax construction.\n\nWe could add a new keyword `var` that will be used to declare and\ninitialize variables in the local scope, like this:\n\n```js\nvar i = \"0\"\n```\n\nWhile the current syntax:\n\n```js\ni = \"0\"\n```\n\nWill be assigning a new value to an already existent variable **i**.\nThe assignment will first look for the target variable in the local\nscope and then in the parent, traversing the entire stack, until it's\nfound and then updated, otherwise (in case the variable is not found)\nthe interpreter must abort with error.\n\n```sh\nvar count = \"0\" # declare local variable\n\nfn inc() {\n\t# update outer variable\n\tcount, _ <= expr $count \"+\" 1\n}\n\ninc()\nprint($count) \t# outputs: 1\n```\n\nBelow is how this proposal solves the list example:\n\n```sh\nfn list() {\n\t# initialize an \"l\" variable in this scope\n\tvar l = ()\n\n\tfn add(val) {\n\t\t# use the \"l\" variable from parent scope\n\t\t# find first in the this scope if not found\n\t\t# then find variable in the parent scope\n\t\tl <= append($l, $val)\n\t}\n\n\tfn get(i) {\n\t\t# use the \"l\" variable from parent scope\n\t\treturn $l[$i]\n\t}\n\n\tfn string() {\n\t\t# use the \"l\" variable from parent scope\n\t\tprint(\"list: [%s]\\n\", $l)\n\t}\n\n\tfn not_clear() {\n\t\t# force initialize a new \"l\" variable in this scope\n\t\t# because this the \"l\" list in the parent scope is not cleared\n\t\tvar l = ()\n\t}\n\n\treturn $add, $get, $string\n}\n```\n\nSyntactically, the `var` statement is an extension of the assignment\nand exec-assignment statements, and then it should support multiple\ndeclarations in a single statement also. Eg.:\n\n```sh\nvar i, j = \"0\", \"1\"\n\nvar body, err <= curl -f $url\n\nvar name, surname, err <= getAuthor()\n```\n\nUsing var always creates new variables, shadowing previous ones,\nfor example:\n\n\n```sh\nvar a, b = \"0\", \"1\" # works fine, variables didn't existed before\n\nvar a, b, c = \"4\", \"5\", \"6\" # works! too, creating new a, b, c\n```\n\nOn a dynamic typed language there is very little difference between\ncreating a new var or just reassigning it since variables are just\nreferences that store no type information at all. For example,\nwhat is the difference between this:\n\n```\nvar a = \"1\"\na = ()\n```\n\nAnd this ?\n\n```\nvar a = \"1\"\nvar a = ()\n```\n\nThe behavior will be exactly the same, there is no semantic error\non reassigning the same variable to a value with a different type,\nso reassigning on redeclaring has no difference at all (although it\nmakes sense for statically typed languages).\n\nStatements are evaluated in order, so this:\n\n```\na = ()\nvar a = \"1\"\n```\n\nIs **NOT** the same as this:\n\n```\nvar a = \"1\"\nvar a = ()\n```\n\nThis is easier to understand when using closures, let's go\nback to our list implementation, we had something like this:\n\n```\nvar l = ()\n\nfn add(val) {\n        # use the \"l\" variable from parent scope\n        # find first in the this scope if not found\n        # then find variable in the parent scope\n        l <= append($l, $val)\n}\n```\n\nIf we write this:\n\n```\nvar l = ()\n\nfn add(val) {\n        # creates new var\n        var l = ()\n        # manipulates new l var\n        l <= append($l, $val)\n}\n```\n\nThe **add** function will not manipulate the **l** variable from the\nouter scope, and our list implementation will not work properly.\n\nBut writing this:\n\n```\nvar l = ()\n\nfn add(val) {\n        # manipulates outer l var\n        l <= append($l, $val)\n        # creates new var that is useless\n        var l = ()\n}\n```\n\nWill work, since we assigned a new value to the outer **l**\nbefore creating a new **l** var.\n\nThe approach described here is very similar to how variables\nare handled in [Lua](https://www.lua.org/), with the exception\nthat Lua uses the **local** keyword, instead of var.\n\nAlso, Lua allows global variables to be created by default, on\nNash we prefer to avoid global stuff and produce an error when\nassigning new values to variables that do not exist.\n\nSummarizing, on this proposal creating new variables is explicit\nand referencing existent variables on outer scopes is implicit.\n\n\n## Proposal II - Manipulate outer scope explicitly\n\nThis proposal adds a new `outer` keyword that permits the update of\nvariables in the outer scope. The default and implicit behavior of\nvariable assignments is to always create a new variable.\n\nConsidering our list example:\n\n```sh\nfn list() {\n\t# initialize an \"l\" variable in this scope\n\tl = ()\n\n\tfn add(val) {\n\t\t# use the \"l\" variable from the parent\n\t\touter l <= append($l, $val)\n\t}\n\n\tfn get(i) {\n\t\t# use the \"l\" variable from the parent outer l\n\t\treturn $l[$i]\n\t}\n\n\tfn string() {\n\t\t# use the \"l\" variable from the parent outer l\n\t\tprint(\"list: [%s]\\n\", $l)\n\t}\n\n\treturn $add, $get, $string\n}\n```\n\nThe `outer` keyword has the same meaning that Python's `global`\nkeyword.\n\nDifferent from Python global, outer must appear on all assignments,\nlike this:\n\n```sh\nfn list() {\n\t# initialize an \"l\" variable in this scope\n\tl = ()\n\n\tfn doubleadd(val) {\n\t\touter l <= append($l, $val)\n\t\touter l <= append($l, $val)\n\t}\n\n\treturn $doubleadd\n}\n```\n\nThis would be buggy and only add once:\n\n```sh\nfn list() {\n\t# initialize an \"l\" variable in this scope\n\tl = ()\n\n\tfn doubleadd(val) {\n\t\touter l <= append($l, $val)\n\t\tl <= append($l, $val)\n\t}\n\n\treturn $doubleadd\n}\n```\n\nTrying to elaborate more on possible combinations\nwhen using the **outer** keyword we get at some hard\nquestions, like what does outer means on this case:\n\n```\nfn list() {\n    # initialize an \"l\" variable in this scope\n    l = ()\n    fn doubleadd(val) {\n        l <= append($l, $val)\n        outer l <= append($l, $val)\n    }\n    return $doubleadd\n}\n```\n\nWill outer just handle the reference on its own scope or\nwill it jump its own scope and manipulate the outer variable ?\n\nThe name outer implies that it will manipulate the outer scope,\nbypassing its own current scope, but how do you read the outer\nvariable ? We would need to support something like:\n\n```\nfn list() {\n    # initialize an \"l\" variable in this scope\n    l = ()\n    fn add(val) {\n        l <= \"whatever\"\n        outer l <= append(outer $l, $val)\n    }\n    return $doubleadd\n}\n```\n\nIt is like with outer we are bypassing the lexical semantics\nof the code, the order of declarations is not relevant anymore\nsince you have a form of \"goto\" to jump the current scope.\n\n## Comparing both approaches\n\nAs everything in life, the design space for how to handle\nscope management is full of tradeoffs.\n\nMaking outer scope management explicit makes declaring\nnew variables easier, since you have to type less to\ncreate new vars.\n\nBut managing scope using closures gets more cumbersome,\nconsider this nested closures with the **outer** keyword:\n\n```sh\nfn list() {\n\tl = ()\n\n\tfn add(val) {\n\t\t# use the \"l\" variable from the parent\n\t\touter l <= append($l, $val)\n\t\tfn addagain() {\n\t\t        outer l <= append($l, $val)\n\t\t}\n\t\treturn $addagain\n\t}\n\n\treturn $add\n}\n```\n\nAnd this one with **var** :\n\n```sh\nfn list() {\n\tvar l = ()\n\n\tfn add(val) {\n\t\t# use the \"l\" variable from the parent\n\t\tl <= append($l, $val)\n\t\tfn addagain() {\n\t\t        l <= append($l, $val)\n\t\t}\n\t\treturn $addagain\n\t}\n\n\treturn $add\n}\n```\n\nThe **var** option requires more writing for the common\ncase of declaring new variables (specially on the interactive shell\nthis is pretty annoying), but makes closures pretty\nnatural to write, you just manipulate the variables\nthat exists lexically on your scope, like you would do\ninside a **if** or **for** block.\n\nThinking about cognition, it seems easier to write buggy code\nby forgetting to add an **outer** on the code than forgetting\nto add a **var** and by mistake manipulate an variable outside\nthe scope.\n\nThe decision to break if the variable does not exist also enhances\nthe **var** option as less buggy since no new variable will be\ncreated if you forget the **var**, but lexically reachable variables\nwill be manipulated (this is ameliorated by the fact that we don't have\nglobal variables).\n\nIf we go for **outer** it seems that we are going to write less,\nbut some code, involving closures, will be harder to read (and write).\nSince code is usually read more than it is written it seems like a sensible\nchoice to optimize for readability and understandability than just\nsave a few keystrokes.\n\nBut any statements made about cognition are really hard to be\nconsidered as a global truth, since all human beings are biased which makes\nidentification of common patterns of cognition really hard. But if software\ndesign has any kind of goal, must be this =).\n"
  },
  {
    "path": "proposal/2-concurrency.md",
    "content": "# Proposal: Concurrency on Nash\n\nThere has been some discussion on how to provide concurrency to nash.\nThere is a [discussion here](https://github.com/madlambda/nash/issues/224) \non how concurrency could be added as a set of built-in functions.\n\nAs we progressed discussing it seemed desirable to have a concurrency\nthat enforced no sharing between concurrent functions. It eliminates\nraces and forces all communication to happen explicitly, and the\nperformance overhead would not be a problem to a high level language\nas nash.\n\n## Lightweight Processes\n\nThis idea is inspired on Erlang concurrency model. Since Nash does\nnot aspire to do everything that Erlang does (like distributed programming)\nso this is not a copy, we just take some things as inspiration.\n\nWhy call this a process ?\nOn the [Erlang docs](http://erlang.org/doc/getting_started/conc_prog.html)\nthere is a interesting definition of process:\n\n```\nthe term \"process\" is usually used when the threads of execution share no\ndata with each other and the term \"thread\" when they share data in some way.\nThreads of execution in Erlang share no data,\nthat is why they are called processes\n```\n\nIn this context the process word is used to mean a concurrent thread of\nexecution that does not share any data. The only means of communication\nare through message passing. Since these processes are lightweight\ncreating a lot of them will be cheap (at least must cheaper than\nOS processes).\n\nInstead of using channel instances in this model you send messages\nto processes (actor model), it works pretty much like a networking\nmodel using UDP datagrams.\n\nThe idea is to leverage this as a syntactic construction of the language\nto make it as explicit and easy as possible to use.\n\nThis idea introduces 4 new concepts, 3 built-in functions and one\nnew keyword.\n\nThe keyword **spawn** is used to spawn a function as a new process.\nThe function **send** is used to send messages to a process.\nThe function **receive** is used to receive messages from a process.\nThe function **self** returns the pid of the process calling it.\n\nAn example of a simple ping/pong:\n\n```\npid <= spawn fn () {\n    ping, senderpid <= receive()\n    echo $ping\n    send($senderpid, \"pong\")\n}()\n\nsend($pid, \"ping\", self())\npong <= receive()\n\necho $pong\n```\n\nSpawned functions can also receive parameters (always deep copies):\n\n```\npid <= spawn fn (answerpid) {\n    send($answerpid, \"pong\")\n}(self())\n\npong <= receive()\necho $pong\n```\n\nA simple fan-out/fan-in implementation (N jobs <-> N processes):\n\n```\njobs = (\"1\" \"2\" \"3\" \"4\" \"5\")\n\nfor job in $jobs {\n    spawn fn (job, answerpid) {\n        import io\n\n        io_println(\"job[%s] done\", $job)\n        send($answerpid, format(\"result [%s]\", $job))\n    }($job, self())\n}\n\nfor job in $jobs {\n    result <= receive()\n    echo $result\n}\n```\n\nAll output (stdout and stderr) of processes go to their\nparent until the root (main) process, so printing inside\na child process will print on the stdout of the main process.\n\n### Advanced Fan-out Fan-in\n\nHere is an example of a more elaborated fan-out/fan-in.\nOn this case we have much more jobs to execute than\nworkers, so it requires more coordination than the previous example.\n\nFor brevity this example does not handle timeouts.\n\nLets suppose an script that tries different passwords on a host:\n\n```\nvar passwords_feed <= spawn fn() {\n\n    fn sendpassword(password) {\n        var worker <= receive()\n        if !send($worker, $password) {\n            sendpassword($password)\n        }\n    }\n\n    for password in generate_passwords() {\n        sendpassword($password)\n    }\n}\n\nfn login(output, passwords_feed, done) {\n\n    for send($passwords_feed, self()) {\n        var password = receive()\n        var result <= login \"someuser\" $password\n        send($output, $result)\n    }\n\n    send($done, \"done\")\n}\n\nfn outputhandler() {\n    for {\n        var result = receive()\n        if $result == \"0\" {\n            echo \"success\"\n        }\n    }\n}\n\nvar workers = 10\n\nvar feed <= spawn passwords_feed()\nvar outputhandler <= spawn outputhandler()\n\nfor i in range(0, $workers) {\n    spawn login($outputhandler, $feed, self())\n}\n\nfor i in range(0, $workers) {\n    msg <= receive()\n    if $msg != \"done\" {\n        echo \"dafuck ?\"\n    }\n}\n```\n\n### Error Handling\n\nError handling on this concurrency model is very similar to\nhow we do it on a distributed system. If a remote service fails and\njust dies and you are using UDP you will never be informed of it,\nthe behavior will be to timeout the request and try again (possibly\nto another service instance through a load balancer).\n\nTo implement this idea we can add a timeout to the receive an add\na new parameter, a boolean, indicating if there is a message or if a\ntimeout has occurred.\n\nExample:\n\n```\nmsg, ok <= receive(timeout)\nif !ok {\n    echo \"oops timeout\"\n}\n```\n\nThe timeout can be omitted if you wish to just wait forever.\n\nFor send operations we need to add just one boolean return\nvalue indicating if the process pid exists and the message\nhas been delivered:\n\n```\nif !send($pid, $msg) {\n    echo \"oops message cant be sent\"\n}\n```\n\nSince the processes are always local there is no need for a more\ndetailed error message (the message would always be the same), the\nerror will always involve a pid that has no owner (the process never\nexisted or already exited).\n\nWe could add a more specific error message if we decide that\nthe process message queue can get too big and we start to\ndrop messages. The error would help to differentiate\nfrom a dead process or a overloaded process.\n\nAn error indicating a overloaded process could help\nto implement back pressure logic (try again later).\nBut if we are sticking with local concurrency only this\nmay be unnecessary complexity. You can avoid this by\nalways sending N messages and waiting for N responses\nbefore sending more messages.\n\n### TODO\n\nSpawned functions should have access to imported modules ?\n(seems like no, but some usages of this may seem odd)\n\nIf send is never blocking, what if process queue gets too big ?\njust go on until memory exhausts ?\n\nShould send be synchronous how we are going to differentiate\nbetween a timeout or a invalid pid error ? On the other hand\nsynchronous send solves the queueing problem.\n\n## Extend rfork\n\nConverging to a no shared state between concurrent functions initiated\nthe idea of using the current rfork built-in as a means to express\nconcurrency on Nash. This would already be possible today, the idea\nis just to make it even easier, specially the communication between\ndifferent concurrent processes.\n\nThis idea enables an even greater amount of isolation between concurrent\nprocesses since rfork enables different namespaces isolation (besides memory),\nbut it has the obvious fallback of not being very lightweight.\n\nSince the idea of nash is to write simple scripts this does not seem\nto be a problem. If it is on the future we can create lightweight concurrent\nprocesses (green threads) that works orthogonally with rfork.\n\nThe prototype for the new rfork would be something like this:\n\n```sh\nchan <= rfork [ns_param1, ns_param2] (chan) {\n        //some code\n}\n```\n\nThe code on the rfork block does not have access to the\nlexical outer scope but it receives as a parameter a channel\ninstance.\n\nThis channel instance can be used by the forked processes and\nby the creator of the process to communicate. We could use built-in functions:\n\n```sh\nchan <= rfork [ns_param1, ns_param2] (chan) {\n        cwrite($chan, \"hi\")\n}\n\na <= cread($chan)\n```\n\nOr some syntactic extension:\n\n```sh\nchan <= rfork [ns_param1, ns_param2] (chan) {\n        $chan <- \"hi\"\n}\n\na <= <-$chan\n```\n\nSince this channel is meant only to be used to communicate with\nthe created process, it will be closed when the process exit:\n\n```sh\nchan <= rfork [ns_param1, ns_param2] (chan) {\n}\n\n# returns empty string when channel is closed\n<-$chan\n```\n\nFan out and fan in should be pretty trivial:\n\n```sh\nchan1 <= rfork [ns_param1, ns_param2] (chan) {\n}\n\nchan2 <= rfork [ns_param1, ns_param2] (chan) {\n}\n\n# waiting for both to finish\n<-$chan1\n<-$chan2\n```\n"
  },
  {
    "path": "scanner/examples_test.go",
    "content": "package scanner_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/madlambda/nash/scanner\"\n)\n\nfunc Example() {\n\tlex := scanner.Lex(\"-input-\", `echo \"hello world\"`)\n\n\tfor tok := range lex.Tokens {\n\t\tfmt.Println(tok)\n\t}\n\n\t// Output:\n\t// IDENT\n\t// STRING\n\t// ;\n\t// EOF\n\n}\n"
  },
  {
    "path": "scanner/lex.go",
    "content": "// Package scanner is the lexical parser.\npackage scanner\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/madlambda/nash/token\"\n)\n\ntype (\n\tToken struct {\n\t\ttyp token.Token\n\t\ttoken.FileInfo\n\n\t\tval string\n\t}\n\n\tstateFn func(*Lexer) stateFn\n\n\t// Lexer holds the state of the scanner\n\tLexer struct {\n\t\tname  string // identify the source, used only for error reports\n\t\tinput string // the string being scanned\n\t\tstart int    // start position of current token\n\n\t\twidth  int        // width of last rune read\n\t\tTokens chan Token // channel of scanned tokens\n\n\t\t// file positions\n\t\tpos         int // file offset\n\t\tline        int // current line position\n\t\tlineStart   int // line of the symbol's start\n\t\tprevColumn  int // previous column value\n\t\tcolumn      int // current column position\n\t\tcolumnStart int // column of the symbol's start\n\n\t\topenParens int\n\n\t\taddSemicolon bool\n\t}\n)\n\nconst (\n\teof = -1\n)\n\nfunc (i Token) Type() token.Token { return i.typ }\nfunc (i Token) Value() string     { return i.val }\n\nfunc (i Token) String() string {\n\tswitch i.typ {\n\tcase token.Illegal:\n\t\treturn \"ERROR: \" + i.val\n\tcase token.EOF:\n\t\treturn \"EOF\"\n\t}\n\n\tif len(i.typ.String()) > 10 {\n\t\treturn fmt.Sprintf(\"%s...\", i.typ.String()[0:10])\n\t}\n\n\treturn fmt.Sprintf(\"%s\", i.typ)\n}\n\n// run lexes the input by executing state functions until the state is nil\nfunc (l *Lexer) run() {\n\tl.line, l.lineStart, l.column, l.columnStart = 1, 1, 0, 0\n\n\tfor state := lexStart; state != nil; {\n\t\tstate = state(l)\n\t}\n\n\tl.emit(token.EOF)\n\tclose(l.Tokens) // No more tokens will be delivered\n}\n\nfunc (l *Lexer) emitVal(t token.Token, val string, line, column int) {\n\tl.Tokens <- Token{\n\t\tFileInfo: token.NewFileInfo(line, column),\n\n\t\ttyp: t,\n\t\tval: val,\n\t}\n\n\tl.start = l.pos\n\tl.lineStart = l.line\n\tl.columnStart = l.column\n}\n\nfunc (l *Lexer) emit(t token.Token) {\n\tl.Tokens <- Token{\n\t\tFileInfo: token.NewFileInfo(l.lineStart, l.columnStart),\n\n\t\ttyp: t,\n\t\tval: l.input[l.start:l.pos],\n\t}\n\n\tl.start = l.pos\n\tl.lineStart = l.line\n\tl.columnStart = l.column\n}\n\n// peek returns but does not consume the next rune from input\nfunc (l *Lexer) peek() rune {\n\trune := l.next()\n\tl.backup()\n\treturn rune\n}\n\n// next consumes the next rune from input\nfunc (l *Lexer) next() rune {\n\tvar r rune\n\n\tif l.pos >= len(l.input) {\n\t\tl.width = 0\n\t\treturn eof\n\t}\n\n\tr, l.width = utf8.DecodeRuneInString(l.input[l.pos:])\n\n\tl.pos += l.width\n\tl.prevColumn = l.column\n\n\tif r == '\\n' {\n\t\tl.line++\n\t\tl.column = 0\n\t} else {\n\t\tl.column++\n\t}\n\n\treturn r\n}\n\n// ignore skips over the pending input before this point\nfunc (l *Lexer) ignore() {\n\tl.start = l.pos\n\tl.lineStart = l.line\n\tl.columnStart = l.column\n}\n\n// backup steps back one rune\nfunc (l *Lexer) backup() {\n\tl.pos -= l.width\n\n\tr, _ := utf8.DecodeRuneInString(l.input[l.pos:])\n\n\tl.column = l.prevColumn\n\n\tif r == '\\n' {\n\t\tl.line--\n\t}\n}\n\n// acceptRun consumes a run of runes from the valid setup\nfunc (l *Lexer) acceptRun(valid string) {\n\tfor strings.IndexRune(valid, l.next()) >= 0 {\n\n\t}\n\n\tl.backup()\n}\n\n// errorf emit an error token\nfunc (l *Lexer) errorf(format string, args ...interface{}) stateFn {\n\tfname := l.name\n\n\tif fname == \"\" {\n\t\tfname = \"<none>\"\n\t}\n\n\terrMsg := fmt.Sprintf(format, args...)\n\n\targuments := make([]interface{}, 0, len(args)+2)\n\targuments = append(arguments, fname, l.line, l.column, errMsg)\n\n\tl.Tokens <- Token{\n\t\tFileInfo: token.NewFileInfo(l.line, l.column),\n\n\t\ttyp: token.Illegal,\n\t\tval: fmt.Sprintf(\"%s:%d:%d: %s\", arguments...),\n\t}\n\n\tl.start = len(l.input)\n\tl.lineStart = l.line\n\tl.columnStart = l.column\n\tl.pos = l.start\n\n\treturn nil // finish the state machine\n}\n\nfunc Lex(name, input string) *Lexer {\n\tl := &Lexer{\n\t\tname:   name,\n\t\tinput:  input,\n\t\tTokens: make(chan Token),\n\t}\n\n\tgo l.run() // concurrently run state machine\n\n\treturn l\n}\n\nfunc lexStart(l *Lexer) stateFn {\n\tr := l.next()\n\n\tswitch {\n\tcase r == eof:\n\t\tif l.addSemicolon {\n\t\t\tl.emitVal(token.Semicolon, \";\", l.line, l.column)\n\t\t}\n\n\t\tl.addSemicolon = false\n\n\t\treturn nil\n\tcase '0' <= r && r <= '9':\n\t\tdigits := \"0123456789\"\n\n\t\tl.acceptRun(digits)\n\n\t\tnext := l.peek()\n\n\t\t// >[2=]\n\t\t// cmd[2]\n\t\tif next == '=' || next == ']' || (!isIdentifier(l.peek()) && !isArgument(l.peek())) {\n\t\t\tl.emit(token.Number)\n\t\t} else if isIdentifier(l.peek()) {\n\t\t\tabsorbIdentifier(l)\n\n\t\t\tif isArgument(l.peek()) {\n\t\t\t\tabsorbArgument(l)\n\n\t\t\t\tl.emit(token.Arg)\n\t\t\t} else {\n\t\t\t\tl.emit(token.Ident)\n\t\t\t}\n\t\t} else if isArgument(l.peek()) {\n\t\t\tabsorbArgument(l)\n\t\t\tl.emit(token.Arg)\n\t\t}\n\n\t\treturn lexStart\n\tcase r == ';':\n\t\tl.emit(token.Semicolon)\n\t\treturn lexStart\n\tcase isSpace(r):\n\t\treturn lexSpace\n\n\tcase isEndOfLine(r):\n\t\tl.ignore()\n\n\t\tif l.addSemicolon && l.openParens == 0 {\n\t\t\tl.emitVal(token.Semicolon, \";\", l.line, l.column)\n\t\t}\n\n\t\tl.addSemicolon = false\n\n\t\treturn lexStart\n\tcase r == '\"':\n\t\tl.ignore()\n\n\t\treturn lexQuote\n\tcase r == '#':\n\t\treturn lexComment\n\tcase r == '+':\n\t\tl.emit(token.Plus)\n\t\treturn lexStart\n\tcase r == '>':\n\t\tl.emit(token.Gt)\n\t\treturn lexStart\n\tcase r == '|':\n\t\tl.emit(token.Pipe)\n\t\treturn lexStart\n\tcase r == '$':\n\t\tr = l.next()\n\n\t\tif !isIdentifier(r) {\n\t\t\treturn l.errorf(\"Expected identifier, but found %q\", r)\n\t\t}\n\n\t\tabsorbIdentifier(l)\n\n\t\tnext := l.peek()\n\t\tif next != eof && !isSpace(next) &&\n\t\t\t!isEndOfLine(next) && next != ';' &&\n\t\t\tnext != ')' && next != ',' && next != '+' &&\n\t\t\tnext != '[' && next != ']' && next != '(' &&\n\t\t\tnext != '.' {\n\t\t\tl.errorf(\"Unrecognized character in action: %#U\", next)\n\t\t\treturn nil\n\t\t}\n\n\t\tl.emit(token.Variable)\n\t\treturn lexStart\n\tcase r == '=':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\tl.emit(token.Equal)\n\t\t} else {\n\t\t\tl.emit(token.Assign)\n\t\t}\n\n\t\treturn lexStart\n\tcase r == '!':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\tl.emit(token.NotEqual)\n\t\t} else {\n\t\t\tl.emit(token.Arg)\n\t\t}\n\n\t\treturn lexStart\n\tcase r == '<':\n\t\tif l.peek() == '=' {\n\t\t\tl.next()\n\t\t\tl.emit(token.AssignCmd)\n\t\t} else {\n\t\t\tl.emit(token.Lt)\n\t\t}\n\n\t\treturn lexStart\n\tcase r == '{':\n\t\tl.addSemicolon = false\n\t\tl.emit(token.LBrace)\n\t\treturn lexStart\n\tcase r == '}':\n\t\tl.emit(token.RBrace)\n\t\tl.addSemicolon = false\n\t\treturn lexStart\n\tcase r == '[':\n\t\tl.emit(token.LBrack)\n\t\treturn lexStart\n\tcase r == ']':\n\t\tl.emit(token.RBrack)\n\t\treturn lexStart\n\tcase r == '(':\n\t\tl.openParens++\n\n\t\tl.emit(token.LParen)\n\t\tl.addSemicolon = false\n\t\treturn lexStart\n\tcase r == ')':\n\t\tl.openParens--\n\n\t\tl.emit(token.RParen)\n\t\tl.addSemicolon = true\n\t\treturn lexStart\n\tcase r == ',':\n\t\tl.emit(token.Comma)\n\t\treturn lexStart\n\tcase r == '.':\n\t\tdotLine, dotColumn := l.line, l.column\n\t\tnext := l.peek()\n\t\tif next == '.' {\n\t\t\tl.next()\n\t\t\tnext = l.peek()\n\t\t\tif next == '.' {\n\t\t\t\tl.next()\n\t\t\t\tl.emitVal(token.Dotdotdot, \"...\", dotLine, dotColumn)\n\t\t\t\treturn lexStart\n\t\t\t}\n\t\t}\n\t\tabsorbArgument(l)\n\t\tl.emit(token.Arg)\n\t\tif next == eof && l.openParens > 0 {\n\t\t\tl.addSemicolon = false\n\t\t} else {\n\t\t\tl.addSemicolon = true\n\t\t}\n\t\treturn lexStart\n\tcase isIdentifier(r):\n\t\t// nash literals are lowercase\n\t\tabsorbIdentifier(l)\n\n\t\tnext := l.peek()\n\n\t\tif isEndOfLine(next) || isSpace(next) ||\n\t\t\tnext == '=' || next == '(' ||\n\t\t\tnext == ')' || next == ',' ||\n\t\t\tnext == '[' || next == eof {\n\t\t\tlit := scanIdentifier(l)\n\n\t\t\tif len(lit) > 1 && r >= 'a' && r <= 'z' {\n\t\t\t\tl.emit(token.Lookup(lit))\n\t\t\t} else {\n\t\t\t\tl.emit(token.Ident)\n\t\t\t}\n\t\t} else if next == '.' {\n\t\t\t// because of shell idiosyncrasies I've to replicate\n\t\t\t// almost same dotdotdot lex here...\n\t\t\tident := l.input[l.start:l.pos]\n\t\t\tidentLine, identCol := l.lineStart, l.columnStart\n\t\t\tdotLine, dotColumn := l.line, l.column\n\t\t\tl.next()\n\t\t\tnext = l.peek()\n\t\t\tif next == '.' {\n\t\t\t\tl.next()\n\t\t\t\tnext = l.peek()\n\t\t\t\tif next == '.' {\n\t\t\t\t\tl.next()\n\t\t\t\t\tl.emitVal(token.Ident, ident, identLine, identCol)\n\t\t\t\t\tl.emitVal(token.Dotdotdot, \"...\", dotLine, dotColumn)\n\t\t\t\t\treturn lexStart\n\t\t\t\t}\n\t\t\t}\n\t\t\tabsorbArgument(l)\n\t\t\tl.emit(token.Arg)\n\t\t} else {\n\t\t\tabsorbArgument(l)\n\t\t\tl.emit(token.Arg)\n\t\t}\n\n\t\tif next == eof && l.openParens > 0 {\n\t\t\tl.addSemicolon = false\n\t\t} else {\n\t\t\tl.addSemicolon = true\n\t\t}\n\n\t\treturn lexStart\n\tcase isArgument(r):\n\t\tabsorbArgument(l)\n\t\tl.emit(token.Arg)\n\t\tl.addSemicolon = true\n\t\treturn lexStart\n\t}\n\n\treturn l.errorf(\"Unrecognized character in action: %#U\", r)\n}\n\nfunc absorbIdentifier(l *Lexer) {\n\tfor {\n\t\tr := l.next()\n\n\t\tif isIdentifier(r) {\n\t\t\tcontinue // absorb\n\t\t}\n\n\t\tbreak\n\t}\n\n\tl.backup() // pos is now ahead of the alphanum\n}\n\nfunc absorbArgument(l *Lexer) {\n\tfor {\n\t\tr := l.next()\n\n\t\tif isArgument(r) {\n\t\t\tcontinue // absorb\n\t\t}\n\n\t\tbreak\n\t}\n\n\tl.backup() // pos is now ahead of the alphanum\n}\n\nfunc scanIdentifier(l *Lexer) string {\n\tabsorbIdentifier(l)\n\n\treturn l.input[l.start:l.pos]\n}\n\nfunc lexQuote(l *Lexer) stateFn {\n\tvar data []rune\n\n\tdata = make([]rune, 0, 256)\n\n\tfor {\n\t\tr := l.next()\n\n\t\tif r != '\"' && r != eof {\n\t\t\tif r == '\\\\' {\n\t\t\t\tr = l.next()\n\n\t\t\t\tswitch r {\n\t\t\t\tcase 'n':\n\t\t\t\t\tdata = append(data, '\\n')\n\t\t\t\tcase 't':\n\t\t\t\t\tdata = append(data, '\\t')\n\t\t\t\tcase '\\\\':\n\t\t\t\t\tdata = append(data, '\\\\')\n\t\t\t\tcase '\"':\n\t\t\t\t\tdata = append(data, '\"')\n\t\t\t\tcase 'x', 'u', 'U':\n\t\t\t\t\treturn l.errorf(\"Escape types 'x', 'u' and 'U' aren't implemented yet\")\n\t\t\t\tcase '0', '1', '2', '3', '4', '5', '6', '7':\n\t\t\t\t\tx := r - '0'\n\n\t\t\t\t\tfor i := 2; i > 0; i-- {\n\t\t\t\t\t\tr = l.next()\n\n\t\t\t\t\t\tif r >= '0' && r <= '7' {\n\t\t\t\t\t\t\tx = x*8 + r - '0'\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn l.errorf(\"non-octal character in escape sequence: %c\", r)\n\t\t\t\t\t}\n\n\t\t\t\t\tif x > 255 {\n\t\t\t\t\t\treturn l.errorf(\"octal escape value > 255: %d\", x)\n\t\t\t\t\t}\n\n\t\t\t\t\tdata = append(data, x)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata = append(data, r)\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif r == eof {\n\t\t\treturn l.errorf(\"Quoted string not finished: %s\", l.input[l.start:])\n\t\t}\n\n\t\tl.emitVal(token.String, string(data), l.lineStart, l.columnStart)\n\n\t\tl.ignore() // ignores last quote\n\t\tbreak\n\t}\n\n\treturn lexStart\n}\n\nfunc lexComment(l *Lexer) stateFn {\n\tfor {\n\t\tr := l.next()\n\n\t\tif isEndOfLine(r) {\n\t\t\tl.backup()\n\t\t\tl.emit(token.Comment)\n\n\t\t\tbreak\n\t\t}\n\n\t\tif r == eof {\n\t\t\tl.backup()\n\t\t\tl.emit(token.Comment)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn lexStart\n}\n\nfunc lexSpace(l *Lexer) stateFn {\n\tignoreSpaces(l)\n\treturn lexStart\n}\n\nfunc ignoreSpaces(l *Lexer) {\n\tfor {\n\t\tr := l.next()\n\n\t\tif !isSpace(r) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tl.backup()\n\tl.ignore()\n}\n\n// isSpace reports whether r is a space character.\nfunc isSpace(r rune) bool {\n\treturn r == ' ' || r == '\\t'\n}\n\nfunc isArgument(r rune) bool {\n\tisId := isAlpha(r)\n\n\treturn isId || (r != eof && !isEndOfLine(r) && !isSpace(r) &&\n\t\tr != '$' && r != '{' && r != '}' && r != '(' && r != ']' && r != '[' &&\n\t\tr != ')' && r != '>' && r != '\"' && r != ',' && r != ';' && r != '|')\n}\n\nfunc isIdentifier(r rune) bool {\n\treturn isAlpha(r) || r == '_'\n}\n\n// isIdentifier reports whether r is a valid identifier\nfunc isAlpha(r rune) bool {\n\treturn unicode.IsLetter(r) || unicode.IsDigit(r)\n}\n\n// isEndOfLine reports whether r is an end-of-line character.\nfunc isEndOfLine(r rune) bool {\n\treturn r == '\\r' || r == '\\n'\n}\n"
  },
  {
    "path": "scanner/lex_regression_test.go",
    "content": "package scanner\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/token\"\n)\n\nfunc TestLexerIssue34(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cat\"},\n\t\t{typ: token.Arg, val: \"/etc/passwd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.Arg, val: \"/dev/null\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test-issue-34\", `cat /etc/passwd > /dev/null echo \"hello world\"`, expected, t)\n}\n\nfunc TestLexerIssue21(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.Variable, val: \"$outFname\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test redirection variable\", `cmd > $outFname`, expected, t)\n}\n\nfunc TestLexerIssue22(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"gocd\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"path\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.String, val: \"\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Else, val: \"else\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/src/\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test issue 22\", `fn gocd(path) {\n    if $path == \"\" {\n        cd $GOPATH\n    } else {\n        cd $GOPATH + \"/src/\" + $path\n    }\n}`, expected, t)\n}\n\nfunc TestLexerIssue19(t *testing.T) {\n\tline := `version = \"4.5.6\"\ncanonName <= echo -n $version | sed \"s/\\\\.//g\"`\n\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"version\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"4.5.6\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"canonName\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"-n\"},\n\t\t{typ: token.Variable, val: \"$version\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"sed\"},\n\t\t{typ: token.String, val: \"s/\\\\.//g\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF}}\n\n\ttestTable(\"test issue 19\", line, expected, t)\n}\n\nfunc TestLexerIssue38(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/src/\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test issue38\", `cd($GOPATH + \"/src/\" + $path)`, expected, t)\n}\n\nfunc TestLexerIssue43(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"gpull\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"branch\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"git\"},\n\t\t{typ: token.Arg, val: \"rev-parse\"},\n\t\t{typ: token.Arg, val: \"--abbrev-ref\"},\n\t\t{typ: token.Ident, val: \"HEAD\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"xargs\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"-n\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"git\"},\n\t\t{typ: token.Ident, val: \"pull\"},\n\t\t{typ: token.Ident, val: \"origin\"},\n\t\t{typ: token.Variable, val: \"$branch\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"refreshPrompt\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test issue #41\", `fn gpull() {\n        branch <= git rev-parse --abbrev-ref HEAD | xargs echo -n\n\n        git pull origin $branch\n        refreshPrompt()\n}`, expected, t)\n}\n\nfunc TestLexerIssue68(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cat\"},\n\t\t{typ: token.Ident, val: \"PKGBUILD\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"sed\"},\n\t\t{typ: token.String, val: \"s#\\\\\\\\$pkgdir#/home/i4k/alt#g\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.Ident, val: \"PKGBUILD2\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test issue #68\", `cat PKGBUILD | sed \"s#\\\\\\\\$pkgdir#/home/i4k/alt#g\" > PKGBUILD2`, expected, t)\n}\n\nfunc TestLexerIssue85(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Arg, val: \"-echo\"},\n\t\t{typ: token.Ident, val: \"hello\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test issue 85\", `a <= -echo hello`, expected, t)\n}\n\nfunc TestLexerIssue69(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$a\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"b\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test69\", `a = ($a + \"b\")`, expected, t)\n\n}\n\nfunc TestLexerIssue127(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Illegal, val: \"test127:1:12: Unrecognized character in action: U+002F '/'\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test127\", `rm -rf $HOME/.vim`, expected, t)\n}\n"
  },
  {
    "path": "scanner/lex_test.go",
    "content": "package scanner\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/madlambda/nash/token\"\n)\nimport \"testing\"\n\nfunc testTable(name, content string, expected []Token, t *testing.T) {\n\tl := Lex(name, content)\n\n\tif l == nil {\n\t\tt.Errorf(\"Failed to initialize lexer\")\n\t\treturn\n\t}\n\n\tif l.Tokens == nil {\n\t\tt.Errorf(\"Failed to initialize lexer\")\n\t\treturn\n\t}\n\n\tresult := make([]Token, 0, 1024)\n\n\tfor i := range l.Tokens {\n\t\tresult = append(result, i)\n\t}\n\n\tif len(result) != len(expected) {\n\t\tt.Errorf(\"Failed to parse commands, length differs %d != %d\",\n\t\t\tlen(result), len(expected))\n\n\t\tfmt.Printf(\"Parsing content: %s\\n\", content)\n\n\t\tfor _, res := range result {\n\t\t\tfmt.Printf(\"parsed: %+v\\n\", res)\n\t\t}\n\n\t\tfmt.Printf(\"\\n\")\n\n\t\tfor _, exp := range expected {\n\t\t\tfmt.Printf(\"expect: %+v\\n\", exp)\n\t\t}\n\n\t\treturn\n\t}\n\n\tfor i := 0; i < len(expected); i++ {\n\t\tif expected[i].typ != result[i].typ {\n\t\t\tt.Errorf(\"'%s (%s)' != '%s (%s)'\", expected[i].typ, expected[i].val, result[i].typ, result[i].val)\n\t\t\treturn\n\t\t}\n\n\t\tif expected[i].val != result[i].val {\n\t\t\tt.Errorf(\"Parsing '%s':\\n\\terror: '%s' != '%s'\", content, expected[i].val, result[i].val)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestLexerCommandStringArgs(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Ident, val: \"hello\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test args\", `echo hello\necho \"hello\"`, expected, t)\n}\n\nfunc TestLexerTokenToString(t *testing.T) {\n\tit := Token{\n\t\ttyp: token.EOF,\n\t}\n\n\tif it.String() != \"EOF\" {\n\t\tt.Errorf(\"Wrong eof string: %s\", it.String())\n\t}\n\n\tit = Token{\n\t\ttyp: token.Illegal,\n\t\tval: \"some error\",\n\t}\n\n\tif it.String() != \"ERROR: some error\" {\n\t\tt.Errorf(\"wrong error string: %s\", it.String())\n\t}\n\n\tit = Token{\n\t\ttyp: token.Ident,\n\t\tval: \"echo\",\n\t}\n\n\tif it.String() != \"IDENT\" {\n\t\tt.Errorf(\"wrong command name: %s\", it.String())\n\t}\n\n\tit = Token{\n\t\ttyp: token.Ident,\n\t\tval: \"echoooooooooooooooooooooooo\",\n\t}\n\n\t// test if long names are truncated\n\tif it.String() != \"IDENT\" {\n\t\tt.Errorf(\"wrong command name: %s\", it.String())\n\t}\n}\n\nfunc TestLexerShebangOnly(t *testing.T) {\n\texpected := []Token{\n\t\t{\n\t\t\ttyp: token.Comment,\n\t\t\tval: \"#!/bin/nash\",\n\t\t},\n\t\t{\n\t\t\ttyp: token.EOF,\n\t\t},\n\t}\n\n\ttestTable(\"testShebangonly\", \"#!/bin/nash\\n\", expected, t)\n}\n\nfunc TestLexerSimpleSetEnvAssignment(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.SetEnv, val: \"setenv\"},\n\t\t{typ: token.Ident, val: \"name\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSet\", `setenv name`, expected, t)\n}\n\nfunc TestLexerSimpleAssignment(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testAssignment\", `test=\"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test = \"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test          =\"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test=           \"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test\t=\"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test\t\t=\"value\"`, expected, t)\n\ttestTable(\"testAssignment spacy\", `test =\t\"value\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"other\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"other\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple separate assignments\", `\n        test=\"value\"\n        other=\"other\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"other\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Variable, val: \"$test\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Variable, val: \"$other\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple separate assignments\", `\n        test=\"value\"\n        other=$test\n        echo $other`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"STALI_SRC\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Variable, val: \"$PWD\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/src\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test underscore\", `STALI_SRC = $PWD + \"/src\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"PROMPT\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"(\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \")\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$PROMPT\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test concat with parenthesis\", `PROMPT = \"(\"+$path+\")\"+$PROMPT`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"0\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test index assignment\", `a[0] = \"test\"`, expected, t)\n\n}\n\nfunc TestLexerListAssignment(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"plan9\"},\n\t\t{typ: token.Ident, val: \"from\"},\n\t\t{typ: token.Ident, val: \"bell\"},\n\t\t{typ: token.Ident, val: \"labs\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testListAssignment\", \"test=( plan9 from bell labs )\", expected, t)\n\ttestTable(\"testListAssignment no space\", \"test=(plan9 from bell labs)\", expected, t)\n\ttestTable(\"testListAssignment multiline\", `test = (\n\tplan9\n\tfrom\n\tbell\n\tlabs\n)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"plan9\"},\n\t\t{typ: token.Ident, val: \"from\"},\n\t\t{typ: token.String, val: \"bell\"},\n\t\t{typ: token.Ident, val: \"labs\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testListAssignment mixed args\", `test=( \"plan9\" from \"bell\" labs )`, expected, t)\n\ttestTable(\"testListAssignment mixed args\", `test=(\"plan9\" from \"bell\" labs)`, expected, t)\n\ttestTable(\"testListAssignment mixed args\", `test = (\n        \"plan9\"\n        from\n        \"bell\"\n        labs\n)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$plan9\"},\n\t\t{typ: token.Ident, val: \"from\"},\n\t\t{typ: token.Variable, val: \"$bell\"},\n\t\t{typ: token.Ident, val: \"labs\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testListAssignment mixed args\", `test=( $plan9 from $bell labs )`, expected, t)\n\ttestTable(\"testListAssignment mixed args\", `test=($plan9 from $bell labs)`, expected, t)\n\ttestTable(\"testListAssignment mixed args\", `test = (\n        $plan9\n        from\n        $bell\n        labs\n)`, expected, t)\n}\n\nfunc TestLexerListOfLists(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"l\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testlistoflists\", `l = (())`, expected, t)\n\ttestTable(\"testlistoflists\", `l = (\n\t\t()\n\t)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"l\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"plan9\"},\n\t\t{typ: token.Ident, val: \"from\"},\n\t\t{typ: token.Ident, val: \"bell\"},\n\t\t{typ: token.Ident, val: \"labs\"},\n\t\t{typ: token.RParen, val: \")\"},\n\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"linux\"},\n\t\t{typ: token.RParen, val: \")\"},\n\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testlistoflists\", `l = ((plan9 from bell labs) (linux))`, expected, t)\n\ttestTable(\"testlistoflists\", `l = (\n\t\t(plan9 from bell labs)\n\t\t(linux)\n\t)`, expected, t)\n\n}\n\nfunc TestLexerInvalidAssignments(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"value\"},\n\t\t{typ: token.Ident, val: \"other\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testInvalidAssignments\", `test=\"value\" other`, expected, t)\n}\n\nfunc TestLexerSimpleCommand(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `echo \"hello world\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"rootfs-x86_64\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `echo rootfs-x86_64`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"git\"},\n\t\t{typ: token.Ident, val: \"clone\"},\n\t\t{typ: token.Arg, val: \"--depth=1\"},\n\t\t{typ: token.Arg, val: \"http://git.sta.li/toolchain\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `git clone --depth=1 http://git.sta.li/toolchain`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `ls $GOPATH`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/src/github.com\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `ls $GOPATH+\"/src/github.com\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.String, val: \"/src/github.com\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `ls \"/src/github.com\"+$GOPATH`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.String, val: \"/home/user\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/.gvm/pkgsets/global/src\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `ls \"/home/user\" + \"/.gvm/pkgsets/global/src\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Arg, val: \"./rkt\"},\n\t\t{typ: token.Semicolon, val: \";\"}, // automatic semicolon insertion\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test local command\", \"./rkt\", expected, t)\n\n}\n\nfunc TestLexerPipe(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"wc\"},\n\t\t{typ: token.Arg, val: \"-l\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testPipe\", `ls | wc -l`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.Arg, val: \"-l\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"wc\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"awk\"},\n\t\t{typ: token.String, val: \"{print $1}\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testPipe\", `ls -l | wc | awk \"{print $1}\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"go\"},\n\t\t{typ: token.Ident, val: \"tool\"},\n\t\t{typ: token.Ident, val: \"vet\"},\n\t\t{typ: token.Arg, val: \"-h\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"grep\"},\n\t\t{typ: token.Ident, val: \"log\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testPipe with redirection\", `go tool vet -h >[2=1] | grep log`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"go\"},\n\t\t{typ: token.Ident, val: \"tool\"},\n\t\t{typ: token.Ident, val: \"vet\"},\n\t\t{typ: token.Arg, val: \"-h\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.Arg, val: \"out.log\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"grep\"},\n\t\t{typ: token.Ident, val: \"log\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testPipe with redirection\", `go tool vet -h > out.log | grep log`, expected, t)\n}\n\nfunc TestPipeFunctions(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"some thing\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"replace\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \" \"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.String, val: \"|\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test pipe with function\",\n\t\t`echo \"some thing\" | replace(\" \", \"|\")`,\n\t\texpected, t)\n}\n\nfunc TestLexerUnquoteArg(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Ident, val: \"hello\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `echo hello`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"hello-world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `echo hello-world`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Comment, val: \"#hello-world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testSimpleCommand\", `echo #hello-world`, expected, t)\n}\n\nfunc TestLexerDashedCommand(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Arg, val: \"google-chrome\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testDashedCommand\", `google-chrome`, expected, t)\n}\n\nfunc TestLexerPathCommand(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Arg, val: \"/bin/echo\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testPathCommand\", `/bin/echo \"hello world\"`, expected, t)\n}\n\nfunc TestLexerInvalidBlock(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testInvalidBlock\", \"{\", expected, t)\n}\n\nfunc TestLexerQuotedStringNotFinished(t *testing.T) {\n\texpected := []Token{\n\t\t{\n\t\t\ttyp: token.Ident,\n\t\t\tval: \"echo\",\n\t\t},\n\t\t{\n\t\t\ttyp: token.Illegal,\n\t\t\tval: \"testQuotedstringnotfinished:1:17: Quoted string not finished: hello world\",\n\t\t},\n\t\t{\n\t\t\ttyp: token.EOF,\n\t\t},\n\t}\n\n\ttestTable(\"testQuotedstringnotfinished\", \"echo \\\"hello world\", expected, t)\n}\n\nfunc TestLexerVariousCommands(t *testing.T) {\n\tcontent := `\n            echo \"hello world\"\n            mount -t proc proc /proc\n        `\n\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\n\t\t{typ: token.Ident, val: \"mount\"},\n\t\t{typ: token.Arg, val: \"-t\"},\n\t\t{typ: token.Ident, val: \"proc\"},\n\t\t{typ: token.Ident, val: \"proc\"},\n\t\t{typ: token.Arg, val: \"/proc\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testVariouscommands\", content, expected, t)\n}\n\nfunc TestLexerRfork(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Rfork, val: \"rfork\"},\n\t\t{typ: token.Ident, val: \"u\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testRfork\", \"rfork u\\n\", expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Rfork, val: \"rfork\"},\n\t\t{typ: token.Ident, val: \"usnm\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"inside namespace :)\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testRforkWithBlock\", `\n            rfork usnm {\n                echo \"inside namespace :)\"\n            }\n        `, expected, t)\n\n}\n\nfunc TestLexerSomethingIdontcareanymore(t *testing.T) {\n\t// maybe oneliner rfork isnt a good idea\n\texpected := []Token{\n\t\t{typ: token.Rfork, val: \"rfork\"},\n\t\t{typ: token.Ident, val: \"u\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test whatever\", \"rfork u { ls }\", expected, t)\n}\n\nfunc TestLexerBuiltinCd(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.String, val: \"some place\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testBuiltinCd\", `cd \"some place\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Arg, val: \"/proc\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testBuiltinCdNoQuote\", `cd /proc`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testBuiltincd home\", `cd`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"HOME\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"/\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.SetEnv, val: \"setenv\"},\n\t\t{typ: token.Ident, val: \"HOME\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"pwd\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"testBuiltin cd bug\", `\n\t               HOME=\"/\"\n                       setenv HOME\n\t               cd\n\t               pwd\n\t           `, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test builtin cd into variable\", `cd $GOPATH`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Variable, val: \"$GOPATH\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"/src/github.com\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test cd with concat\", `cd $GOPATH+\"/src/github.com\"`, expected, t)\n}\n\nfunc TestLexerRedirectSimple(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.Arg, val: \"file.out\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple redirect\", \"cmd > file.out\", expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.String, val: \"tcp://localhost:8888\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple redirect\", `cmd > \"tcp://localhost:8888\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.String, val: \"udp://localhost:8888\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple redirect\", `cmd > \"udp://localhost:8888\"`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.String, val: \"unix:///tmp/sock.txt\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple redirect\", `cmd > \"unix:///tmp/sock.txt\"`, expected, t)\n\n}\n\nfunc TestLexerRedirectMap(t *testing.T) {\n\n\t// Suppress stderr output\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", \"cmd >[2=]\", expected, t)\n\n\t// points stderr to stdout\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test stderr=stdout\", \"cmd >[2=1]\", expected, t)\n}\n\nfunc TestLexerRedirectMapToLocation(t *testing.T) {\n\t// Suppress stderr output\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"file.out\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", \"cmd >[2=] file.out\", expected, t)\n\n\t// points stderr to stdout\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"file.out\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test stderr=stdout\", \"cmd >[2=1] file.out\", expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"/var/log/service.log\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", `cmd >[2] /var/log/service.log`, expected, t)\n}\n\nfunc TestLexerRedirectMultipleMaps(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"file.out\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"file.err\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", `cmd >[1] file.out >[2] file.err`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Arg, val: \"/var/log/service.log\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", `cmd >[2=1] >[1] /var/log/service.log`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test suppress stderr\", `cmd >[1=2] >[2=]`, expected, t)\n}\n\nfunc TestLexerImport(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Import, val: \"import\"},\n\t\t{typ: token.Arg, val: \"env.sh\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test import\", `import env.sh`, expected, t)\n}\n\nfunc TestLexerSimpleIf(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.String, val: \"other\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple if\", `if \"test\" == \"other\" { rm -rf / }`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.NotEqual, val: \"!=\"},\n\t\t{typ: token.Variable, val: \"$test\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple if\", `if \"test\" != $test {\n            rm -rf /\n        }`, expected, t)\n\n\ttestTable(\"test simple if\", `\n\n        if \"test\" != $test {\n            rm -rf /\n        }`, expected, t)\n}\n\nfunc TestLexerIfWithConcat(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.Variable, val: \"$test\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \"001\"},\n\t\t{typ: token.NotEqual, val: \"!=\"},\n\t\t{typ: token.String, val: \"value001\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test if concat\", `if $test + \"001\" != \"value001\" {\n        rm -rf /\n}`, expected, t)\n}\n\nfunc TestLexerIfWithFuncInvocation(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"some val\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.NotEqual, val: \"!=\"},\n\t\t{typ: token.String, val: \"value001\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test if concat\", `if test(\"some val\") != \"value001\" {\n        rm -rf /\n}`, expected, t)\n}\n\nfunc TestLexerIfElse(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.String, val: \"other\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Else, val: \"else\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"pwd\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple if\", `if \"test\" == \"other\" { rm -rf / } else { pwd }`, expected, t)\n}\n\nfunc TestLexerIfElseIf(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.String, val: \"other\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"rm\"},\n\t\t{typ: token.Arg, val: \"-rf\"},\n\t\t{typ: token.Arg, val: \"/\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Else, val: \"else\"},\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.Variable, val: \"$var\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"pwd\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Else, val: \"else\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"exit\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple if\", `\n        if \"test\" == \"other\" {\n                rm -rf /\n        } else if \"test\" == $var {\n                pwd\n        } else {\n                exit 1\n        }`, expected, t)\n}\n\nfunc TestLexerFnBasic(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test empty fn\", `fn build() {}`, expected, t)\n\n\t// lambda\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test empty fn\", `fn () {}`, expected, t)\n\n\t// IIFE\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test empty fn\", `fn () {}()`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"image\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"debug\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test empty fn with args\", `fn build(image, debug) {}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"image\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"debug\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"ls\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"tar\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test empty fn with args and body\", `fn build(image, debug) {\n            ls\n            tar\n        }`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"path\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"PROMPT\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"(\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$path\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.String, val: \")\"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$PROMPT\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.SetEnv, val: \"setenv\"},\n\t\t{typ: token.Ident, val: \"PROMPT\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test cd fn with PROMPT update\", `fn cd(path) {\n    cd $path\n    PROMPT=\"(\" + $path + \")\"+$PROMPT\n    setenv PROMPT\n}`, expected, t)\n}\n\nfunc TestLexerFuncall(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn invocation\", `build()`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"ubuntu\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn invocation\", `build(\"ubuntu\")`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"ubuntu\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Variable, val: \"$debug\"},\n\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn invocation\", `build(\"ubuntu\", $debug)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"build\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$debug\"},\n\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn invocation\", `build($debug)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"b\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn composition\", `a(b())`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ids_luns\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"append\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$ids_luns\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$id\"},\n\t\t{typ: token.Variable, val: \"$lun\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test katcipis bad mood\", `ids_luns <= append($ids_luns, ($id $lun))`,\n\t\texpected, t)\n}\n\nfunc TestLexerAssignCmdOut(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"ipaddr\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"someprogram\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test assignCmdOut\", `ipaddr <= someprogram`, expected, t)\n}\n\nfunc TestLexerMultipleAssignCmdOut(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"ret\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"status\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"someprogram\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple return assignCmdOut\", `ret, status <= someprogram`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ret\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"_\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"someprogram\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple return assignCmdOut\", `ret, _ <= someprogram`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"ret\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"obj\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"err\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.Ident, val: \"somefn\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple return assignCmdOut\", `ret, obj, err <= somefn()`, expected, t)\n}\n\nfunc TestMultipleAssignments(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"b\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"1\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.String, val: \"2\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiple assign\", `a, b = \"1\", \"2\"`, expected, t)\n}\n\nfunc TestLexerBindFn(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.BindFn, val: \"bindfn\"},\n\t\t{typ: token.Ident, val: \"cd\"},\n\t\t{typ: token.Ident, val: \"cd2\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test bindfn\", `bindfn cd cd2`, expected, t)\n\n}\n\nfunc TestLexerRedirectionNetwork(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Gt, val: \">\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.String, val: \"tcp://localhost:6667\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test redirection network\", `echo \"hello world\" >[1] \"tcp://localhost:6667\"`, expected, t)\n}\n\nfunc TestLexerDump(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Dump, val: \"dump\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test dump\", `dump`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Dump, val: \"dump\"},\n\t\t{typ: token.Ident, val: \"out\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test dump\", `dump out`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Dump, val: \"dump\"},\n\t\t{typ: token.Variable, val: \"$out\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test dump\", `dump $out`, expected, t)\n}\n\nfunc TestLexerReturn(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", \"return\", expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", \"fn test() { return }\", expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() {\n\treturn\n}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.String, val: \"some value\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() { return \"some value\"}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.String, val: \"some value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() {\n\treturn \"some value\"\n}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"value\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"some value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.Variable, val: \"$value\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() {\n\tvalue = \"some value\"\n\treturn $value\n}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"test\"},\n\t\t{typ: token.String, val: \"test2\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() {\n\treturn (\"test\" \"test2\")\n}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Return, val: \"return\"},\n\t\t{typ: token.Variable, val: \"$PWD\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test return\", `fn test() {\n\treturn $PWD\n}`, expected, t)\n}\n\nfunc TestLexerFor(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.For, val: \"for\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test inf loop\", `for {}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.For, val: \"for\"},\n\t\t{typ: token.Ident, val: \"f\"},\n\t\t{typ: token.Ident, val: \"in\"},\n\t\t{typ: token.Variable, val: \"$files\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test inf loop\", `for f in $files {}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.For, val: \"for\"},\n\t\t{typ: token.Ident, val: \"f\"},\n\t\t{typ: token.Ident, val: \"in\"},\n\t\t{typ: token.Ident, val: \"getfiles\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"/\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test inf loop\", `for f in getfiles(\"/\") {}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.For, val: \"for\"},\n\t\t{typ: token.Ident, val: \"f\"},\n\t\t{typ: token.Ident, val: \"in\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Number, val: \"1\"},\n\t\t{typ: token.Number, val: \"2\"},\n\t\t{typ: token.Number, val: \"3\"},\n\t\t{typ: token.Number, val: \"4\"},\n\t\t{typ: token.Number, val: \"5\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test inf loop\", `for f in (1 2 3 4 5) {}`, expected, t)\n}\n\nfunc TestLexerFnAsFirstClass(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"printer\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"val\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"-n\"},\n\t\t{typ: token.Variable, val: \"$val\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Fn, val: \"fn\"},\n\t\t{typ: token.Ident, val: \"success\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"print\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"val\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Variable, val: \"$print\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"[SUCCESS] \"},\n\t\t{typ: token.Plus, val: \"+\"},\n\t\t{typ: token.Variable, val: \"$val\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.Ident, val: \"success\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$printer\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.String, val: \"Command executed!\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test fn as first class\", `\n        fn printer(val) {\n                echo -n $val\n        }\n\n        fn success(print, val) {\n                $print(\"[SUCCESS] \" + $val)\n        }\n\n        success($printer, \"Command executed!\")\n        `, expected, t)\n}\n\nfunc TestLexerListIndexing(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Variable, val: \"$commands\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"0\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\tfor i := 0; i < 1000; i++ {\n\t\texpected[4] = Token{\n\t\t\ttyp: token.Number,\n\t\t\tval: strconv.Itoa(i),\n\t\t}\n\n\t\ttestTable(\"test variable indexing\", `cmd = $commands[`+strconv.Itoa(i)+`]`, expected, t)\n\t}\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Variable, val: \"$commands\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Arg, val: \"a\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test invalid number\", `cmd = $commands[a]`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"cmd\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.Variable, val: \"$commands\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test invalid number\", `cmd = $commands[]`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Ident, val: \"test\"},\n\t\t{typ: token.Variable, val: \"$names\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"666\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test variable index on commands\", `echo test $names[666]`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.If, val: \"if\"},\n\t\t{typ: token.Variable, val: \"$crazies\"},\n\t\t{typ: token.LBrack, val: \"[\"},\n\t\t{typ: token.Number, val: \"0\"},\n\t\t{typ: token.RBrack, val: \"]\"},\n\t\t{typ: token.Equal, val: \"==\"},\n\t\t{typ: token.String, val: \"patito\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \":D\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test if with indexing\", `if $crazies[0] == \"patito\" { echo \":D\" }`, expected, t)\n}\n\nfunc TestLexerMultilineCmdExecution(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"multiline\", `()`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"multiline\", `(echo)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"BBBBB\"},\n\t\t{typ: token.Ident, val: \"BBBBB\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test multiline cmd execution\", `(echo AAAAA AAAAA\n\tAAAAA AAAAA\n\tAAAAA AAAAA\n\tBBBBB BBBBB)`, expected, t)\n}\n\nfunc TestLexerMultilineCmdAssign(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"some\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"multiline\", `some <= ()`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"some\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"multiline\", `some <= (echo)`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"some\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"AAAAA\"},\n\t\t{typ: token.Ident, val: \"BBBBB\"},\n\t\t{typ: token.Ident, val: \"BBBBB\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"multiline\", `some <= (echo AAAAA AAAAA\n\tAAAAA AAAAA\n\tAAAAA AAAAA\n\tBBBBB BBBBB)`, expected, t)\n\n\ttestTable(\"multiline\", `some <= (\n\techo AAAAA AAAAA\n\tAAAAA AAAAA\n\tAAAAA AAAAA\n\tBBBBB BBBBB\n)`, expected, t)\n}\n\nfunc TestLexerCommandDelimiter(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"hello\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.String, val: \"world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"semicolons to separate commands\",\n\t\t`echo \"hello\"; echo \"world\"`, expected, t)\n}\n\nfunc TestLexerLongAssignment(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"grpid\"},\n\t\t{typ: token.AssignCmd, val: \"<=\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"aws\"},\n\t\t{typ: token.Ident, val: \"ec2\"},\n\t\t{typ: token.Arg, val: \"create-security-group\"},\n\t\t{typ: token.Arg, val: \"--group-name\"},\n\t\t{typ: token.Variable, val: \"$name\"},\n\t\t{typ: token.Arg, val: \"--description\"},\n\t\t{typ: token.Variable, val: \"$desc\"},\n\t\t{typ: token.Variable, val: \"$vpcarg\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"jq\"},\n\t\t{typ: token.String, val: \".GroupId\"},\n\t\t{typ: token.Pipe, val: \"|\"},\n\t\t{typ: token.Ident, val: \"xargs\"},\n\t\t{typ: token.Ident, val: \"echo\"},\n\t\t{typ: token.Arg, val: \"-n\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test xxx\", `grpid <= (\n\taws ec2 create-security-group\n\t\t\t\t--group-name $name\n\t\t\t\t--description $desc\n\t\t\t\t$vpcarg |\n\tjq \".GroupId\" |\n\txargs echo -n)`, expected, t)\n}\n\nfunc TestLexerVarArgs(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Ident, val: \"println\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Ident, val: \"fmt\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Ident, val: \"args\"},\n\t\t{typ: token.Dotdotdot, val: \"...\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.LBrace, val: \"{\"},\n\t\t{typ: token.Ident, val: \"print\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.Variable, val: \"$fmt\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.Variable, val: \"$args\"},\n\t\t{typ: token.Dotdotdot, val: \"...\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.RBrace, val: \"}\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test var args\", `println(fmt, args...) {\n\tprint($fmt, $args...)\n}`, expected, t)\n\ttestTable(\"test var args\", `println(fmt, args ...) {\n\tprint($fmt, $args...)\n}`, expected, t)\n\n\texpected = []Token{\n\t\t{typ: token.Ident, val: \"print\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"%s:%s:%s\"},\n\t\t{typ: token.Comma, val: \",\"},\n\t\t{typ: token.LParen, val: \"(\"},\n\t\t{typ: token.String, val: \"a\"},\n\t\t{typ: token.String, val: \"b\"},\n\t\t{typ: token.String, val: \"c\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Dotdotdot, val: \"...\"},\n\t\t{typ: token.RParen, val: \")\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test literal expansion\", `print(\"%s:%s:%s\", (\"a\" \"b\" \"c\")...)`,\n\t\texpected, t)\n\ttestTable(\"test literal expansion\", `print(\"%s:%s:%s\", (\"a\" \"b\" \"c\") ...)`,\n\t\texpected, t)\n}\n\nfunc TestLexerVar(t *testing.T) {\n\texpected := []Token{\n\t\t{typ: token.Var, val: \"var\"},\n\t\t{typ: token.Ident, val: \"a\"},\n\t\t{typ: token.Assign, val: \"=\"},\n\t\t{typ: token.String, val: \"hello world\"},\n\t\t{typ: token.Semicolon, val: \";\"},\n\t\t{typ: token.EOF},\n\t}\n\n\ttestTable(\"test simple var decl\", `var a = \"hello world\"`, expected, t)\n}\n"
  },
  {
    "path": "sh/obj.go",
    "content": "package sh\n\nimport \"fmt\"\n\n//go:generate stringer -type=objType\nconst (\n\tStringType objType = iota + 1\n\tFnType\n\tListType\n)\n\ntype (\n\tobjType int\n\n\tObj interface {\n\t\tType() objType\n\t\tString() string\n\t}\n\n\tListObj struct {\n\t\tobjType\n\t\tlist []Obj\n\t}\n\n\tFnObj struct {\n\t\tobjType\n\t\tfn FnDef\n\t}\n\n\tStrObj struct {\n\t\tobjType\n\t\trunes []rune\n\t}\n\n\tCollection interface {\n\t\tLen() int\n\t\tGet(index int) (Obj, error)\n\t}\n\n\tWriteableCollection interface {\n\t\tSet(index int, val Obj) error\n\t}\n)\n\nfunc NewCollection(o Obj) (Collection, error) {\n\tsizer, ok := o.(Collection)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"SizeError: trying to get size from type %s which is not a collection\",\n\t\t\to.Type(),\n\t\t)\n\t}\n\treturn sizer, nil\n}\n\nfunc NewWriteableCollection(o Obj) (WriteableCollection, error) {\n\tindexer, ok := o.(WriteableCollection)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"IndexError: trying to use a non write/indexable type %s to write on index: \",\n\t\t\to.Type(),\n\t\t)\n\t}\n\treturn indexer, nil\n}\n\nfunc (o objType) Type() objType {\n\treturn o\n}\n\nfunc NewStrObj(val string) *StrObj {\n\treturn &StrObj{\n\t\trunes:   []rune(val),\n\t\tobjType: StringType,\n\t}\n}\n\nfunc (o *StrObj) Str() string { return string(o.runes) }\n\nfunc (o *StrObj) String() string { return o.Str() }\n\nfunc (o *StrObj) Get(index int) (Obj, error) {\n\tif index >= o.Len() {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"IndexError: Index[%d] out of range, string size[%d]\",\n\t\t\tindex,\n\t\t\to.Len(),\n\t\t)\n\t}\n\n\treturn NewStrObj(string(o.runes[index])), nil\n}\n\nfunc (o *StrObj) Len() int {\n\treturn len(o.runes)\n}\n\nfunc NewFnObj(val FnDef) *FnObj {\n\treturn &FnObj{\n\t\tfn:      val,\n\t\tobjType: FnType,\n\t}\n}\n\nfunc (o *FnObj) Fn() FnDef { return o.fn }\n\nfunc (o *FnObj) String() string { return fmt.Sprintf(\"<fn %s>\", o.fn.Name()) }\n\nfunc NewListObj(val []Obj) *ListObj {\n\treturn &ListObj{\n\t\tlist:    val,\n\t\tobjType: ListType,\n\t}\n}\n\nfunc (o *ListObj) Len() int {\n\treturn len(o.list)\n}\n\nfunc (o *ListObj) Set(index int, value Obj) error {\n\tif index >= len(o.list) {\n\t\treturn fmt.Errorf(\n\t\t\t\"IndexError: Index[%d] out of range, list size[%d]\",\n\t\t\tindex,\n\t\t\tlen(o.list),\n\t\t)\n\t}\n\to.list[index] = value\n\treturn nil\n}\n\nfunc (o *ListObj) Get(index int) (Obj, error) {\n\tif index >= len(o.list) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"IndexError: Index out of bounds, index[%d] but list size[%d]\",\n\t\t\tindex,\n\t\t\tlen(o.list),\n\t\t)\n\t}\n\treturn o.list[index], nil\n}\n\nfunc (o *ListObj) List() []Obj { return o.list }\n\nfunc (o *ListObj) String() string {\n\tresult := \"\"\n\tlist := o.List()\n\tfor i := 0; i < len(list); i++ {\n\t\tl := list[i]\n\n\t\tresult += l.String()\n\n\t\tif i < len(list)-1 {\n\t\t\tresult += \" \"\n\t\t}\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "sh/objtype_string.go",
    "content": "// Code generated by \"stringer -type=objType\"; DO NOT EDIT\n\npackage sh\n\nimport \"fmt\"\n\nconst _objType_name = \"StringTypeFnTypeListType\"\n\nvar _objType_index = [...]uint8{0, 10, 16, 24}\n\nfunc (i objType) String() string {\n\ti -= 1\n\tif i < 0 || i >= objType(len(_objType_index)-1) {\n\t\treturn fmt.Sprintf(\"objType(%d)\", i+1)\n\t}\n\treturn _objType_name[_objType_index[i]:_objType_index[i+1]]\n}\n"
  },
  {
    "path": "sh/shell.go",
    "content": "package sh\n\nimport \"io\"\n\ntype (\n\tRunner interface {\n\t\tStart() error\n\t\tWait() error\n\t\tResults() []Obj\n\n\t\tSetArgs([]Obj) error\n\t\tSetEnviron([]string)\n\t\tSetStdin(io.Reader)\n\t\tSetStdout(io.Writer)\n\t\tSetStderr(io.Writer)\n\n\t\tStdoutPipe() (io.ReadCloser, error)\n\n\t\tStdin() io.Reader\n\t\tStdout() io.Writer\n\t\tStderr() io.Writer\n\t}\n\n\tFnArg struct {\n\t\tName       string\n\t\tIsVariadic bool\n\t}\n\n\tFn interface {\n\t\tName() string\n\t\tArgNames() []FnArg\n\n\t\tRunner\n\n\t\tString() string\n\t}\n\n\tFnDef interface {\n\t\tName() string\n\t\tArgNames() []FnArg\n\t\tBuild() Fn\n\t}\n)\n\nfunc NewFnArg(name string, isVariadic bool) FnArg {\n\treturn FnArg{\n\t\tName:       name,\n\t\tIsVariadic: isVariadic,\n\t}\n}\n"
  },
  {
    "path": "spec.ebnf",
    "content": "/* Nash program */\nprogram = { statement } .\n\n/* Statement */\nstatement = varDecl | command | fnInv | builtin | comment .\n\n/* Variable declaration */\nvarDecl        = assignValue | assignCmdOut .\nassignValue    = identifierList \"=\" varSpecList .\nidentifierList = identifier [ \",\" identifierList ] .\nvarSpecList    = varSpec [ \",\" varSpecList ] .\nvarSpec        = ( list | string ) .\nstring         = stringLit | ( stringConcat { stringConcat } ) .\nassignCmdOut   = identifier \"<=\" ( command | fnInv ) .\n\n/* Command */\ncommand   = ( [ \"(\" ] cmdpart [ \")\" ]  | pipe ) .\ncmdpart   = [ \"-\" ] ( cmdname | abscmd ) { argument } { redirect } .\ncmdname   = identifier .\nabscmd    = filename .\nargument  = ( unicode_char { unicode_char } ) | stringLit .\npipe      = [ \"(\" ] cmdpart \"|\" cmdpart [ { \"|\" cmdpart } ] [ \")\" ] .\nredirect    = ( \">\" ( filename | uri | variable ) |\n               \">\" \"[\" unicode_digit \"]\" ( filename | uri | variable ) |\n               \">\" \"[\" unicode_digit \"=\" ( unicode_digit | identifier ) \"]\" |\n               \">\" \"[\" unicode_digit \"=\" \"]\" ) .\n\n/* Builtin */\nbuiltin = importDecl | rforkDecl | ifDecl | forDecl | setenvDecl |\n          fnDecl | bindfn | dump .\n\n/* Import statement */\nimportDecl = \"import\" ( filename | stringLit ) .\n\n/* Rfork scope */\nrforkDecl   = \"rfork\" rforkFlags \"{\" program \"}\" .\nrforkFlags  = { identifier } .\n\n/* If-else-if */\nifDecl = \"if\" ( variable | string ) comparison\n              ( variable | string ) \"{\" program \"}\"\n         [ \"else\" \"{\" program \"}\" ]\n         [ \"else\" ifDecl ] .\n\n/* For loop */\nforDecl = \"for\" [ identifier \"in\" ( list | variable | fnInv) ] \"{\" program \"}\" .\n\n/* Function declaration */\nfnDecl = \"fn\" identifier \"(\" fnArgs \")\" \"{\"\n         program [ returnDecl ]\n         \"}\" .\nfnArgs = { fnArg [ \",\" ] } .\nfnArg  = identifier [ \"...\" ] .\n\n/* return declaration */\nreturnDecl = \"return\" [ ( variable | stringLit | list | fnInv ) ] .\n\n/* Function invocation */\nfnInv = ( variable | identifier ) \"(\" fnArgValues \")\" .\n\nfnArgValues = { fnArgValue [ \",\" ] } .\nfnArgValue  = [ stringLit | stringConcat | list | (variable [ \"...\" ]) | (list [ \"...\" ]) fnInv ] .\n\n/* Function binding */\nbindfn = \"bindfn\" identifier identifier .\n\n/* dump shell state */\ndump = \"dump\" [ filename ] .\n\n/* Set environment variable */\nsetenvDecl = \"setenv\" ( identifier | varDecl ) .\n\n/* Comment */\ncomment = \"#\" { unicode_char } .\n\n/* Lists */\nlist = \"(\" { argument } \")\" .\n\nletter      = unicode_letter | \"_\" .\nfilename    = { [ \"/\" ]  { unicode_letter } } .\nipaddr      = unicode_digit { unicode_digit } \".\"\n              unicode_digit { unicode_digit } \".\"\n              unicode_digit { unicode_digit } \".\"\n              unicode_digit { unicode_digit } \".\" .\nport        = unicode_digit { unicode_digit } .\nnetworkaddr = ipaddr \":\" port .\nlocation    = filename | networkaddr .\nschema      = \"file\" | \"tcp\" | \"udp\" | \"unix\" .\nuri         = schema \"://\" location .\n\nidentifier  = letter { letter | unicode_digit } .\nvariable    = \"$\" identifier .\n\ncomparison  = \"==\" | \"!=\" .\n\nstringLit   = \"\\\"\" { unicode_char | newline } \"\\\"\" .\n\nstringConcat = ( stringLit | variable ) \"+\" (stringLit | variable ) .\n\n/* terminals */\nnewline        = /* the Unicode code point U+000A */ .\nunicode_char   = /* an arbitrary Unicode code point except newline */ .\nunicode_letter = /* a Unicode code point classified as \"Letter\" */ .\nunicode_digit  = /* a Unicode code point classified as \"Number, decimal digit\" */ .\n"
  },
  {
    "path": "spec_test.go",
    "content": "package nash\n\nimport (\n\t\"bytes\"\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests\"\n\t\"golang.org/x/exp/ebnf\"\n)\n\nfunc TestSpecificationIsSane(t *testing.T) {\n\tfilename := filepath.Join(tests.Projectpath, \"spec.ebnf\")\n\tcontent, err := ioutil.ReadFile(filename)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tbuf := bytes.NewBuffer(content)\n\tgrammar, err := ebnf.Parse(filename, buf)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\terr = ebnf.Verify(grammar, \"program\")\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "stdbin/mkdir/main.go",
    "content": "package main \n\nimport (\n\t\"os\"\n\t\"fmt\"\n)\n\nfunc mkdirs(dirnames []string) error {\n\tfor _, d := range dirnames {\n\t\tif err := os.MkdirAll(d, 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tfmt.Fprintf(os.Stderr, \"usage: %s <dir1> <dir2> ...\\n\", os.Args[0])\n\t\tos.Exit(1)\n\t}\n\terr := mkdirs(os.Args[1:])\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n}"
  },
  {
    "path": "stdbin/mkdir/mkdir_test.go",
    "content": "package main \n\nimport (\n\t\"testing\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"io/ioutil\"\n\t\"os\"\n)\n\ntype testcase struct {\n\tdirs []string\n}\n\nfunc testMkdir(t *testing.T, tc testcase) {\n\ttmpDir, err := ioutil.TempDir(\"\", \"nash-mkdir\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer os.RemoveAll(tmpDir)\n\tvar dirs []string\n\tfor _, p := range tc.dirs {\n\t\tdirs = append(dirs, filepath.FromSlash(path.Join(tmpDir, p)))\n\t}\n\n\terr = mkdirs(dirs)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, d := range dirs {\n\t\tif s, err := os.Stat(d); err != nil {\n\t\t\tt.Fatal(err)\n\t\t} else if s.Mode()&os.ModeDir != os.ModeDir {\n\t\t\tt.Fatalf(\"Invalid directory mode: %v\", s.Mode())\n\t\t}\n\t}\n}\n\nfunc TestMkdir(t *testing.T) {\n\tfor _, tc := range []testcase{\n\t\t{\n\t\t\tdirs: []string{},\n\t\t},\n\t\t{\n\t\t\tdirs: []string{\n\t\t\t\t\"1\", \"2\", \"3\", \"4\", \"5\",\n\t\t\t\t\"some\", \"thing\", \"_random_\",\n\t\t\t\t\"_\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdirs: []string{\"a\", \"a\"}, // directory already exists, silently works\n\t\t},\n\t} {\n\t\ttc := tc \n\t\ttestMkdir(t, tc)\n\t}\n}"
  },
  {
    "path": "stdbin/pwd/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %s\", err)\n\t\tos.Exit(1)\n\t}\n\tfmt.Println(wd)\n}\n"
  },
  {
    "path": "stdbin/strings/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {\n\n\tconst defaultMinTextSize = 4\n\tvar minTextSize uint\n\n\tflag.UintVar(\n\t\t&minTextSize,\n\t\t\"s\",\n\t\tdefaultMinTextSize,\n\t\t\"the minimum size in runes to characterize as a text\",\n\t)\n\n\tscanner := Do(os.Stdin, minTextSize)\n\tfor scanner.Scan() {\n\t\tfmt.Println(scanner.Text())\n\t}\n\tif scanner.Err() != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %s\", scanner.Err())\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "stdbin/strings/strings.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"unicode/utf8\"\n)\n\nfunc Do(input io.Reader, minTextSize uint) *bufio.Scanner {\n\toutputReader, outputWriter := io.Pipe()\n\tgo searchstrings(input, minTextSize, outputWriter)\n\tscanner := bufio.NewScanner(outputReader)\n\n\tconst maxBufferSize = 1024 * 1024\n\tb := make([]byte, maxBufferSize)\n\tscanner.Buffer(b, maxBufferSize)\n\treturn scanner\n}\n\nfunc searchstrings(input io.Reader, minTextSize uint, output *io.PipeWriter) {\n\n\tnewline := byte('\\n')\n\tsearcher := wordSearcher{minTextSize: minTextSize}\n\n\twrite := func(data []byte) error {\n\t\tdata = append(data, newline)\n\t\tn, err := output.Write(data)\n\t\tif n != len(data) {\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"expected to write[%d] wrote[%d]\",\n\t\t\t\tlen(data),\n\t\t\t\tn,\n\t\t\t)\n\t\t}\n\t\treturn err\n\t}\n\n\thandleIOError := func(err error) bool {\n\t\tif err != nil {\n\t\t\tvar finalwriteerr error\n\n\t\t\tif text, ok := searcher.flushBuffer(); ok {\n\t\t\t\tfinalwriteerr = write(text)\n\t\t\t}\n\t\t\tif err == io.EOF {\n\t\t\t\tif finalwriteerr == nil {\n\t\t\t\t\toutput.Close()\n\t\t\t\t} else {\n\t\t\t\t\toutput.CloseWithError(fmt.Errorf(\n\t\t\t\t\t\t\"error[%s] writing last data\",\n\t\t\t\t\t\tfinalwriteerr,\n\t\t\t\t\t))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\toutput.CloseWithError(err)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tdata := make([]byte, 1)\n\tfor {\n\t\t// WHY: Don't see the point of checking N when reading a single byte\n\t\tn, err := input.Read(data)\n\n\t\tif n <= 0 {\n\t\t\tif handleIOError(err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// WHY:\n\t\t\t// Implementations of Read are discouraged from\n\t\t\t// returning a zero byte count with a nil error,\n\t\t\t// except when len(p) == 0.\n\t\t\t// Callers should treat a return of 0 and nil as\n\t\t\t// indicating that nothing happened; in particular it\n\t\t\t// does not indicate EOF.\n\t\t\tcontinue\n\t\t}\n\n\t\tif text, ok := searcher.next(data[0]); ok {\n\t\t\terr = write(text)\n\t\t}\n\n\t\tif handleIOError(err) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\ntype byteType int\n\ntype wordSearcher struct {\n\tbuffer         []byte\n\tpossibleRune   []byte\n\twaitingForRune bool\n\tminTextSize    uint\n}\n\nconst (\n\tbinaryType byteType = iota\n\tasciiType\n\truneStartType\n)\n\nfunc (w *wordSearcher) next(b byte) ([]byte, bool) {\n\tif w.waitingForRune {\n\t\treturn w.nextRune(b)\n\t}\n\treturn w.nextASCII(b)\n}\n\nfunc (w *wordSearcher) nextRune(b byte) ([]byte, bool) {\n\n\tconst maxUTFSize = 4\n\n\tif b == 0 {\n\t\tw.resetRuneSearch()\n\t\treturn w.flushBuffer()\n\t}\n\n\tif word := string([]byte{b}); utf8.ValidString(word) {\n\t\tw.resetRuneSearch()\n\t\tdata, ok := w.flushBuffer()\n\t\tw.writeOnBuffer(b)\n\t\treturn data, ok\n\t}\n\n\tif utf8.RuneStart(b) {\n\t\tw.resetRuneSearch()\n\t\tdata, ok := w.flushBuffer()\n\t\tw.startRuneSearch(b)\n\t\treturn data, ok\n\t}\n\n\tw.writeOnPossibleRune(b)\n\tif utf8.ValidString(string(w.possibleRune)) {\n\t\tw.writeOnBuffer(w.possibleRune...)\n\t\tw.resetRuneSearch()\n\t\treturn nil, false\n\t}\n\n\tif len(w.possibleRune) == maxUTFSize {\n\t\tw.resetRuneSearch()\n\t\treturn w.flushBuffer()\n\t}\n\n\treturn nil, false\n}\n\nfunc (w *wordSearcher) resetRuneSearch() {\n\tw.waitingForRune = false\n\tw.possibleRune = nil\n}\n\nfunc (w *wordSearcher) nextASCII(b byte) ([]byte, bool) {\n\tswitch bytetype(b) {\n\tcase binaryType:\n\t\t{\n\t\t\treturn w.flushBuffer()\n\t\t}\n\tcase asciiType:\n\t\t{\n\t\t\tw.writeOnBuffer(b)\n\t\t}\n\tcase runeStartType:\n\t\t{\n\t\t\tw.startRuneSearch(b)\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (w *wordSearcher) startRuneSearch(b byte) {\n\tw.waitingForRune = true\n\tw.writeOnPossibleRune(b)\n}\n\nfunc (w *wordSearcher) writeOnBuffer(b ...byte) {\n\tw.buffer = append(w.buffer, b...)\n}\n\nfunc (w *wordSearcher) writeOnPossibleRune(b byte) {\n\tw.possibleRune = append(w.possibleRune, b)\n}\n\nfunc (w *wordSearcher) bufferLenInRunes() uint {\n\treturn uint(len([]rune(string(w.buffer))))\n}\n\nfunc (w *wordSearcher) flushBuffer() ([]byte, bool) {\n\tif len(w.buffer) == 0 {\n\t\treturn nil, false\n\t}\n\tif w.bufferLenInRunes() < w.minTextSize {\n\t\tw.buffer = nil\n\t\treturn nil, false\n\t}\n\tb := w.buffer\n\tw.buffer = nil\n\treturn b, true\n}\n\nfunc bytetype(b byte) byteType {\n\tif b == 0 {\n\t\treturn binaryType\n\t}\n\tif word := string([]byte{b}); utf8.ValidString(word) {\n\t\treturn asciiType\n\t}\n\tif utf8.RuneStart(b) {\n\t\treturn runeStartType\n\t}\n\treturn binaryType\n}\n"
  },
  {
    "path": "stdbin/strings/strings_test.go",
    "content": "package main_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\tstrings \"github.com/madlambda/nash/stdbin/strings\"\n)\n\nfunc TestStrings(t *testing.T) {\n\n\ttype testcase struct {\n\t\tname        string\n\t\tinput       func([]byte) []byte\n\t\toutput      []string\n\t\tminWordSize uint\n\t}\n\n\ttcases := []testcase{\n\t\t{\n\t\t\tname:        \"UTF-8With2Bytes\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"λ\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"UTF-8With3Bytes\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"€\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"€\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"UTF-8With4Bytes\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"𐍈\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"𐍈\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"NonASCIIWordHasOneLessCharThanMin\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"λ\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"NonASCIIWordHasMinWordSize\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"λλ\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"λλ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"WordHasOneLessCharThanMin\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"k\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"WordHasMinWordSize\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"kz\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"kz\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"WordHasOneMoreCharThanMinWordSize\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"ktz\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"ktz\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"StartingWithOneChar\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte(\"k\"), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"EndWithOneChar\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append(bin, []byte(\"k\")...)\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"OneCharInTheMiddle\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\tt := append(bin, []byte(\"k\")...)\n\t\t\t\tt = append(t, bin...)\n\t\t\t\treturn t\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"StartingWithText\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\texpected := \"textOnBeggining\"\n\t\t\t\treturn append([]byte(expected), bin...)\n\t\t\t},\n\t\t\toutput: []string{\"textOnBeggining\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"TextOnMiddle\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\texpected := \"textOnMiddle\"\n\t\t\t\treturn append(bin, append([]byte(expected), bin...)...)\n\t\t\t},\n\t\t\toutput: []string{\"textOnMiddle\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"NonASCIITextOnMiddle\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\texpected := \"λλλ\"\n\t\t\t\treturn append(bin, append([]byte(expected), bin...)...)\n\t\t\t},\n\t\t\toutput: []string{\"λλλ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIAndNonASCII\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\texpected := \"(define (λ (x) (+ x a)))\"\n\t\t\t\treturn append(bin, append([]byte(expected), bin...)...)\n\t\t\t},\n\t\t\toutput: []string{\"(define (λ (x) (+ x a)))\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"TextOnEnd\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\texpected := \"textOnEnd\"\n\t\t\t\treturn append(bin, append([]byte(expected), bin...)...)\n\t\t\t},\n\t\t\toutput: []string{\"textOnEnd\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"JustText\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn []byte(\"justtext\")\n\t\t\t},\n\t\t\toutput: []string{\"justtext\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"JustBinary\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn bin\n\t\t\t},\n\t\t\toutput: []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"TextSeparatedByBinary\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ttext := []byte(\"text\")\n\t\t\t\tt := []byte{}\n\t\t\t\tt = append(t, bin...)\n\t\t\t\tt = append(t, text...)\n\t\t\t\tt = append(t, bin...)\n\t\t\t\tt = append(t, text...)\n\t\t\t\treturn t\n\t\t\t},\n\t\t\toutput: []string{\"text\", \"text\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"NonASCIITextSeparatedByBinary\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ttext := []byte(\"awesomeλ=)\")\n\t\t\t\tt := []byte{}\n\t\t\t\tt = append(t, bin...)\n\t\t\t\tt = append(t, text...)\n\t\t\t\tt = append(t, bin...)\n\t\t\t\tt = append(t, text...)\n\t\t\t\treturn t\n\t\t\t},\n\t\t\toutput: []string{\"awesomeλ=)\", \"awesomeλ=)\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"WordsAreNotAccumulativeBetweenBinData\",\n\t\t\tminWordSize: 2,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\tt := append([]byte(\"k\"), bin...)\n\t\t\t\treturn append(t, byte('t'))\n\t\t\t},\n\t\t\toutput: []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIISeparatedByByteThatLooksLikeUTF\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte{\n\t\t\t\t\t'n',\n\t\t\t\t\trunestart,\n\t\t\t\t\t'k',\n\t\t\t\t}, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"n\", \"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIAfterPossibleFirstByteOfUTF\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte{\n\t\t\t\t\trunestart,\n\t\t\t\t\t'k',\n\t\t\t\t}, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIAfterPossibleSecondByteOfUTF\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte{\n\t\t\t\t\tbyte(0xE2),\n\t\t\t\t\tbyte(0x82),\n\t\t\t\t\t'k',\n\t\t\t\t}, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIAfterPossibleThirdByteOfUTF\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\treturn append([]byte{\n\t\t\t\t\tbyte(0xF0),\n\t\t\t\t\tbyte(0x90),\n\t\t\t\t\tbyte(0x8D),\n\t\t\t\t\t'k',\n\t\t\t\t}, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"k\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"AfterFalseRuneStartRuneStartOnSecondByte\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ti := []byte{byte(0xF0)}\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn append(i, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"AfterFalseRuneStartRuneStartOnThirdByte\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ti := []byte{byte(0xF0), byte(0x90)}\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn append(i, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"AfterFalseRuneStartRuneStartOnFourthByte\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ti := []byte{byte(0xF0), byte(0x90), byte(0x8D)}\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn append(i, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIFakeRuneAndThemRune\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func(bin []byte) []byte {\n\t\t\t\ti := []byte{'v'}\n\t\t\t\ti = append(i, byte(0xF0))\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn append(i, bin...)\n\t\t\t},\n\t\t\toutput: []string{\"v\", \"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIISplittedByZero\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func([]byte) []byte {\n\t\t\t\treturn []byte{'k', 0, 'n', 0, 'v'}\n\t\t\t},\n\t\t\toutput: []string{\"k\", \"n\", \"v\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"RunesSplittedByZero\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func([]byte) []byte {\n\t\t\t\ti := []byte(\"λ\")\n\t\t\t\ti = append(i, 0)\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn i\n\t\t\t},\n\t\t\toutput: []string{\"λ\", \"λ\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"ASCIIAndRunesSplittedByZero\",\n\t\t\tminWordSize: 1,\n\t\t\tinput: func([]byte) []byte {\n\t\t\t\ti := []byte(\"λ\")\n\t\t\t\ti = append(i, 0)\n\t\t\t\ti = append(i, 's')\n\t\t\t\ti = append(i, 0)\n\t\t\t\ti = append(i, []byte(\"λ\")...)\n\t\t\t\treturn i\n\t\t\t},\n\t\t\toutput: []string{\"λ\", \"s\", \"λ\"},\n\t\t},\n\t}\n\n\tminBinChunkSize := 1\n\tmaxBinChunkSize := 128\n\n\tfor _, tcase := range tcases {\n\t\tfor i := minBinChunkSize; i <= maxBinChunkSize; i++ {\n\t\t\tbinsize := i\n\t\t\ttestname := fmt.Sprintf(\"%s/binSize%d\", tcase.name, binsize)\n\t\t\tt.Run(testname, func(t *testing.T) {\n\t\t\t\tbin := newBinary(uint(binsize))\n\t\t\t\tinput := tcase.input(bin)\n\t\t\t\tscanner := strings.Do(bytes.NewBuffer(input), tcase.minWordSize)\n\n\t\t\t\tlines := []string{}\n\t\t\t\tfor scanner.Scan() {\n\t\t\t\t\tlines = append(lines, scanner.Text())\n\t\t\t\t}\n\n\t\t\t\tif len(lines) != len(tcase.output) {\n\t\t\t\t\tt.Errorf(\"wanted size[%d] got size[%d]\", len(tcase.output), len(lines))\n\t\t\t\t\tt.Fatalf(\"wanted[%s] got[%s]\", tcase.output, lines)\n\t\t\t\t}\n\n\t\t\t\tfor i, want := range tcase.output {\n\t\t\t\t\tgot := lines[i]\n\t\t\t\t\tif want != got {\n\t\t\t\t\t\tt.Errorf(\"unexpected line at[%d]\", i)\n\t\t\t\t\t\tt.Errorf(\"wanted[%s] got[%s]\", want, got)\n\t\t\t\t\t\tt.Errorf(\"wantedLines[%s] gotLines[%s]\", tcase.output, lines)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif scanner.Err() != nil {\n\t\t\t\t\tt.Fatalf(\"unexpected error[%s]\", scanner.Err())\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestStringsReadErrorOnFirstByte(t *testing.T) {\n\tvar minWordSize uint = 1\n\tscanner := strings.Do(newFakeReader(func(d []byte) (int, error) {\n\t\treturn 0, errors.New(\"fake injected error\")\n\t}), minWordSize)\n\tassertScannerFails(t, scanner, 0)\n}\n\nfunc TestStringsReadErrorOnSecondByte(t *testing.T) {\n\tvar minWordSize uint = 1\n\tsentFirstByte := false\n\tscanner := strings.Do(newFakeReader(func(d []byte) (int, error) {\n\t\tif sentFirstByte {\n\t\t\treturn 0, errors.New(\"fake injected error\")\n\t\t}\n\t\td[0] = 'k'\n\t\tsentFirstByte = true\n\t\treturn 1, nil\n\t}), minWordSize)\n\tassertScannerFails(t, scanner, 1)\n}\n\nfunc TestStringsReadErrorAfterValidUTF8StartingByte(t *testing.T) {\n\tvar minWordSize uint = 1\n\tsentFirstByte := false\n\tscanner := strings.Do(newFakeReader(func(d []byte) (int, error) {\n\t\tif sentFirstByte {\n\t\t\treturn 0, errors.New(\"fake injected error\")\n\t\t}\n\t\tsentFirstByte = true\n\t\td[0] = runestart\n\t\treturn 1, nil\n\t}), minWordSize)\n\tassertScannerFails(t, scanner, 0)\n}\n\nfunc TestStringsReadCanReturnEOFWithData(t *testing.T) {\n\tvar minWordSize uint = 1\n\twant := byte('k')\n\n\tscanner := strings.Do(newFakeReader(func(d []byte) (int, error) {\n\t\tif len(d) == 0 {\n\t\t\tt.Fatal(\"empty data on Read operation\")\n\t\t}\n\t\td[0] = want\n\t\treturn 1, io.EOF\n\t}), minWordSize)\n\n\tif !scanner.Scan() {\n\t\tt.Fatal(\"unexpected Scan failure\")\n\t}\n\tgot := scanner.Text()\n\tif string(want) != got {\n\t\tt.Fatalf(\"want[%s] != got[%s]\", string(want), got)\n\t}\n}\n\nconst runestart byte = 0xC2\n\ntype FakeReader struct {\n\tread func([]byte) (int, error)\n}\n\nfunc (f *FakeReader) Read(d []byte) (int, error) {\n\tif f.read == nil {\n\t\treturn 0, fmt.Errorf(\"FakeReader has no Read implementation\")\n\t}\n\treturn f.read(d)\n}\n\nfunc newFakeReader(read func([]byte) (int, error)) *FakeReader {\n\treturn &FakeReader{read: read}\n}\n\nfunc assertScannerFails(t *testing.T, scanner *bufio.Scanner, expectedIter uint) {\n\tvar iterations uint\n\tfor scanner.Scan() {\n\t\titerations += 1\n\t}\n\n\tif iterations != expectedIter {\n\t\tt.Fatalf(\"expected[%d] Scan calls, got [%d]\", expectedIter, iterations)\n\t}\n\n\tif scanner.Err() == nil {\n\t\tt.Fatal(\"expected failure on scanner, got none\")\n\t}\n}\n\nfunc newBinary(size uint) []byte {\n\t// WHY: Starting with the most significant bit as 1 helps to test\n\t// UTF-8 corner cases. Don't change this without providing\n\t// testing for this. Not the best way to do this (not explicit)\n\t// but it is what we have for today =).\n\tbin := make([]byte, size)\n\tfor i := 0; i < int(size); i++ {\n\t\tbin[i] = 0xFF\n\t}\n\treturn bin\n}\n"
  },
  {
    "path": "stdbin/write/common_test.sh",
    "content": "# common test routines\n\nfn fatal(msg) {\n    print($msg)\n    exit(\"1\")\n}\n\nfn assert(expected, got, desc) {\n    if $expected != $got {\n        fatal(format(\"%s: FAILED. Expected[%s] but got[%s]\\n\", $desc, $expected, $got))\n    }\n}"
  },
  {
    "path": "stdbin/write/fd.go",
    "content": "//+build !windows\n\npackage main\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\nfunc specialFile(path string) (io.WriteCloser, bool) {\n\tif path == \"/dev/stdout\" {\n\t\treturn os.Stdout, true\n\t} else if path == \"/dev/stderr\" {\n\t\treturn os.Stderr, true\n\t}\n\treturn nil, false\n}\n"
  },
  {
    "path": "stdbin/write/fd_windows.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\nfunc specialFile(path string) (io.WriteCloser, bool) {\n\tif path == \"CON\" { // holycrap!\n\t\treturn os.Stdout, true\n\t}\n\treturn nil, false\n}"
  },
  {
    "path": "stdbin/write/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n)\n\nvar banner = fmt.Sprintf(\"%s <file> <data>\\n\", os.Args[0])\n\nfunc fatal(msg string) {\n\tfmt.Fprintf(os.Stderr, \"%s\", msg)\n\tos.Exit(1)\n}\n\nfunc main() {\n\tif len(os.Args) <= 1 ||\n\t\tlen(os.Args) > 3 {\n\t\tfatal(banner)\n\t}\n\n\tvar (\n\t\tfname = os.Args[1]\n\t\tin    io.Reader\n\t)\n\n\tif len(os.Args) == 2 {\n\t\tin = os.Stdin\n\t} else {\n\t\tin = bytes.NewBufferString(os.Args[2])\n\t}\n\n\terr := write(fname, in)\n\tif err != nil {\n\t\tfatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "stdbin/write/write.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc toabs(path string) (string, error) {\n\tif !filepath.IsAbs(path) {\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tpath = filepath.Join(wd, path)\n\t}\n\treturn path, nil\n}\n\nfunc outfd(fname string) (io.WriteCloser, error) {\n\tfname, err := toabs(fname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar out io.WriteCloser\n\n\tout, ok := specialFile(fname)\n\tif !ok {\n\t\tf, err := os.OpenFile(fname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = f\n\t}\n\treturn out, nil\n}\n\nfunc write(fname string, in io.Reader) (err error) {\n\tout, err := outfd(fname)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\terr2 := out.Close()\n\t\tif err == nil {\n\t\t\terr = err2\n\t\t}\n\t}()\n\n\t_, err = io.Copy(out, in)\n\treturn err\n}\n"
  },
  {
    "path": "stdbin/write/write_linux_test.sh",
    "content": "# linux tests of write command\n\nimport \"./common_test.sh\"\n\n# this test uses only the write binary\nsetenv PATH = \"./stdbin/write\"\n\n# (desc (out err status))\nvar tests = (\n    (\"standard out\" (\"/dev/stdout\" \"hello world\" \"\" \"0\"))\n    (\"standard err\" (\"/dev/stderr\" \"\" \"hello world\" \"0\"))\n)\n\nvar outstr = \"hello world\"\n\nfor test in $tests {\n    var desc = $test[0]\n    var tc = $test[1]\n\n    print(\"testing %s\\n\", $desc)\n\n    var device = $tc[0]\n    var expectedOut = $tc[1]\n    var expectedErr = $tc[2]\n    var expectedSts = $tc[3]\n\n    var out, err, status <= write $device $outstr\n    assert($expectedSts, $status, \"status code\")\n    assert($expectedOut, $out, \"standard output\")\n    assert($expectedErr, $err, \"standard error\")\n}\n"
  },
  {
    "path": "stdbin/write/write_test.sh",
    "content": "import \"./common_test.sh\"\n\nsetenv PATH = \"./stdbin/write:/bin\"\n\n# FIXME: we need our mktemp\nvar nonExistentFile = \"./here-be-dragons\"\n\nfn clean() {\n    _, _ <= rm -f $nonExistentFile\n}\n\nclean()\n\nvar out, err, status <= write $nonExistentFile \"hello\"\nassert(\"\", $out, \"standard out isnt empty\")\nassert(\"\", $err, \"standard err isnt empty\")\nassert(\"0\", $status, \"status is not success\")\n\nvar content, status <= cat $nonExistentFile\nassert(\"0\", $status, \"status is not success\")\nassert(\"hello\", $content, \"file content is wrong\")\n\n# test append\nout, err, status <= write $nonExistentFile \"1\"\nassert(\"\", $out, \"standard out isnt empty\")\nassert(\"\", $err, \"standard err isnt empty\")\nassert(\"0\", $status, \"status is not success\")\n\ncontent, status <= cat $nonExistentFile\nassert(\"0\", $status, \"status is not success\")\nassert(\"hello1\", $content, \"file content is wrong\")\n\nclean()\n"
  },
  {
    "path": "stdlib/io.sh",
    "content": "\nfn io_println(msg, args...) {\n        print($msg + \"\\n\", $args...)\n}\n"
  },
  {
    "path": "stdlib/io_example.sh",
    "content": "import io\n\nif len($ARGS) == \"2\" {\n        io_println($ARGS[1])\n        exit(\"0\")\n}\n\nio_println($ARGS[1], $ARGS[2])\n"
  },
  {
    "path": "stdlib/io_test.sh",
    "content": "\nfn run_example(args...) {\n        var got, status <= ./cmd/nash/nash ./stdlib/io_example.sh $args\n        return $got, $status\n}\n\nfn assert_success(expected, got, status) {\n        if $status != \"0\" {\n                print(\"expected success, but got status code: %s\\n\", $status)\n                exit(\"1\")\n        }\n        if $got != $expected {\n                print(\"expected [%s] got [%s]\\n\", $expected, $got)\n                exit(\"1\")\n        }\n}\n\nfn test_println_format() {\n        var got, status <= run_example(\"hello %s\", \"world\")\n\n        assert_success(\"hello world\", $got, $status)\n}\n\nfn test_println() {\n        var expected = \"pazu\"\n        var got, status <= run_example($expected)\n\n        assert_success($expected, $got, $status)\n}\n\ntest_println_format()\ntest_println()\n"
  },
  {
    "path": "stdlib/map.sh",
    "content": "fn map_new() {\n        return ()\n}\n\nfn map_get(map, key) {\n        return map_get_default($map, $key, \"\")\n}\n\nfn map_iter(map, func) {\n        for entry in $map {\n                $func($entry[0], $entry[1])\n        }\n}\n\nfn map_get_default(map, key, default) {\n        for entry in $map {\n                if $entry[0] == $key {\n                        return $entry[1]\n                }\n        }\n\n        return $default\n}\n\nfn map_add(map, key, val) {\n        for entry in $map {\n                if $entry[0] == $key {\n                        entry[1] = $val\n                        return $map\n                }\n        }\n\n        var tuple = ($key $val)\n        map <= append($map, $tuple)\n        return $map\n}\n\nfn map_del(map, key) {\n\tvar newmap = ()\n\n        for entry in $map {\n                if $entry[0] != $key {\n\t\t\tvar tuple = ($entry[0] $entry[1])\n\t\t\tnewmap <= append($newmap, $tuple)\n                }\n        }\n\n        return $newmap\n}\n"
  },
  {
    "path": "stdlib/map_test.sh",
    "content": "import map\n\nfn expect(map, key, want) {\n        var got <= map_get($map, $key)\n        if $got != $want {\n                echo \"error: got[\"+$got+\"] want[\"+$want+\"]\"\n                exit(\"1\")\n        }\n}\n\nfn test_adding_keys() {\n        var map <= map_new()\n\n        map <= map_add($map, \"key\", \"value\")\n        expect($map, \"key\", \"value\")\n\n        map <= map_add($map, \"key2\", \"value2\")\n        expect($map, \"key2\", \"value2\")\n\n        map <= map_add($map, \"key\", \"override\")\n        expect($map, \"key\", \"override\")\n}\n\nfn test_absent_key_will_have_empty_string_value() {\n        var map <= map_new()\n        expect($map, \"absent\", \"\")\n}\n\nfn test_absent_key_with_custom_default_value() {\n        var map <= map_new()\n        var want = \"hi\"\n        var got <= map_get_default($map, \"absent\", $want)\n        if $got != $want {\n                echo \"error: got[\"+$got+\"] want[\"+$want+\"]\"\n                exit(\"1\")\n        }\n}\n\nfn test_iterates_map() {\n        var map <= map_new()\n        map <= map_add($map, \"key\", \"value\")\n        map <= map_add($map, \"key2\", \"value2\")\n\n        var got <= map_new()\n\n\tfn iter(key, val) {\n\t\tgot <= map_add($got, $key, $val)\n\t}\n\n        map_iter($map, $iter)\n\n        expect($map, \"key\", \"value\")\n        expect($map, \"key2\", \"value2\")\n}\n\nfn test_removing_key() {\n        var map <= map_new()\n\n        map <= map_add($map, \"key\", \"value\")\n        map <= map_add($map, \"key2\", \"value2\")\n\n        expect($map, \"key\", \"value\")\n        expect($map, \"key2\", \"value2\")\n\n        map <= map_del($map, \"key\")\n        expect($map, \"key\", \"\")\n        expect($map, \"key2\", \"value2\")\n}\n\nfn test_removing_absent_key() {\n        var map <= map_new()\n\n        expect($map, \"key\", \"\")\n        map <= map_del($map, \"key\")\n        expect($map, \"key\", \"\")\n}\n\ntest_adding_keys()\ntest_absent_key_will_have_empty_string_value()\ntest_absent_key_with_custom_default_value()\ntest_iterates_map()\ntest_removing_key()\ntest_removing_absent_key()\n"
  },
  {
    "path": "testfiles/ex1.sh",
    "content": "#!/bin/cnt\n\necho \"hello world\"\n"
  },
  {
    "path": "testfiles/fibonacci.sh",
    "content": "#!/usr/bin/env nash\n\n# Recursive fibonacci implementation to find the value\n# at index n in the sequence.\n\n# Some times:\n#   λ> time ./testfiles/fibonacci.sh 1\n#   1\n#   0.00u 0.01s 0.01r \t ./testfiles/fibonacci.sh 1\n#   λ> time ./testfiles/fibonacci.sh 2\n#   1 2\n#   0.01u 0.01s 0.02r \t ./testfiles/fibonacci.sh 2\n#   λ> time ./testfiles/fibonacci.sh 3\n#   1 2 3\n#   0.01u 0.03s 0.03r \t ./testfiles/fibonacci.sh 3\n#   λ> time ./testfiles/fibonacci.sh 4\n#   1 2 3 5\n#   0.04u 0.04s 0.07r \t ./testfiles/fibonacci.sh 4\n#   λ> time ./testfiles/fibonacci.sh 5\n#   1 2 3 5 8\n#   0.09u 0.07s 0.13r \t ./testfiles/fibonacci.sh 5\n#   λ> time ./testfiles/fibonacci.sh 10\n#   1 2 3 5 8 13 21 34 55 89\n#   1.31u 1.18s 2.03r \t ./testfiles/fibonacci.sh 10\n#   λ> time ./testfiles/fibonacci.sh 15\n#   1 2 3 5 8 13 21 34 55 89 144 233 377 610 987\n#   15.01u 13.49s 22.55r \t ./testfiles/fibonacci.sh 15\n#   λ> time ./testfiles/fibonacci.sh 20\n#   1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946\n#   169.27u 155.50s 265.19r \t ./testfiles/fibonacci.sh 20\n\n# a is lower or equal than b?\nfn lte(a, b) {\n\tvar _, st <= test $a -le $b\n\n\treturn $st\n}\n\nfn fib(n) {\n\tif lte($n, \"1\") == \"0\" {\n\t\treturn \"1\"\n\t}\n\n\tvar a, _ <= expr $n - 1\n\tvar b, _ <= expr $n - 2\n\tvar _a <= fib($a)\n\tvar _b <= fib($b)\n\tvar ret, _ <= expr $_a \"+\" $_b\n\n\treturn $ret\n}\n\nfn range(start, end) {\n\tvar seq, _ <= seq $start $end\n\tvar lst <= split($seq, \"\\n\")\n\n\treturn $lst\n}\n\nfor i in range(\"1\", $ARGS[1]) {\n\tprint(\"%s \", fib($i))\n}\n\nprint(\"\\n\")\n"
  },
  {
    "path": "testfiles/sieve.sh",
    "content": "#!/usr/bin/env nash\n\n# Sieve of Erathostenes\n\nfn lt(a, b) {\n\tvar _, st <= test $a -lt $b\n\n\treturn $st\n}\n\nfn gt(a, b) {\n\tvar _, st <= test $a -gt $b\n\n\treturn $st\n}\n\nfn le(a, b) {\n\tvar _, st <= test $a -le $b\n\n\treturn $st\n}\n\nfn sqrt(n) {\n\tvar v, _ <= expr $n * $n\n\n\treturn $v\n}\n\nfn range(start, end) {\n\tvar values, _ <= seq $start $end\n\tvar list <= split($values, \"\\n\")\n\n\treturn $list\n}\n\nfn xrange(start, condfn) {\n\tvar out = ()\n\n\tif $condfn($start) == \"0\" {\n\t\tout = ($start)\n\t} else {\n\t\treturn ()\n\t}\n\n\tvar next = $start\n\n\tfor {\n\t\tnext, _ <= expr $next \"+\" 1\n\n\t\tif $condfn($next) == \"0\" {\n\t\t\tout <= append($out, $next)\n\t\t} else {\n\t\t\treturn $out\n\t\t}\n\t}\n\n\tunreachable\n}\n\nfn sieve(n) {\n\tif lt($n, \"2\") == \"0\" {\n\t\treturn ()\n\t}\n\tif $n == \"2\" {\n\t\treturn (\"2\")\n\t}\n\n\tvar tries = (\"0\" \"0\")\n\n\tfor i in range(\"2\", $n) {\n\t\ttries <= append($tries, \"1\")\n\t}\n\n\tfn untilSqrtRoot(v) {\n\t\treturn le(sqrt($v), $n)\n\t}\n\n\tfor i in xrange(\"2\", $untilSqrtRoot) {\n\t\tif $tries[$i] == \"1\" {\n\t\t\tfor j in range(\"0\", $n) {\n\t\t\t\t# arithmetic seems cryptic without integers =(\n\t\t\t\tvar k, _ <= expr $i * $i \"+\" \"(\" $j * $i \")\"\n\n\t\t\t\tif gt($k, $n) != \"0\" {\n\t\t\t\t\ttries[$k] = \"0\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvar primes = ()\n\n\tfor i in range(\"2\", $n) {\n\t\tif $tries[$i] == \"1\" {\n\t\t\tprimes <= append($primes, $i)\n\t\t}\n\t}\n\n\treturn $primes\n}\n\nfor prime in sieve($ARGS[1]) {\n\tprint(\"%s \", $prime)\n}\n\nprint(\"\\n\")\n"
  },
  {
    "path": "tests/cfg.go",
    "content": "package tests\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nvar (\n\t// Nashcmd is the nash's absolute binary path in source\n\tNashcmd string\n\n\t// Projectpath is the path to nash source code\n\tProjectpath string\n\n\t// Testdir is the test assets directory\n\tTestdir string\n\n\t// Stdbindir is the stdbin directory\n\tStdbindir string\n)\n\nfunc init() {\n\tproject := \"github.com/madlambda/nash\"\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tpanic(\"failed to get current directory\")\n\t}\n\n\tpos := strings.Index(wd, project) + len(project)\n\tProjectpath = wd[:pos]\n\n\tTestdir = filepath.Join(Projectpath, \"testfiles\")\n\tNashcmd = filepath.Join(Projectpath, \"cmd\", \"nash\", \"nash\")\n\tStdbindir = filepath.Join(Projectpath, \"stdbin\")\n\n\tif runtime.GOOS == \"windows\" {\n\t\tNashcmd += \".exe\"\n\t}\n\n\tif _, err := os.Stat(Nashcmd); err != nil {\n\t\tpanic(\"Please, run make build before running tests\")\n\t}\n}\n"
  },
  {
    "path": "tests/doc.go",
    "content": "// Package tests contains all nash tests that are blackbox.\n// What would be blackbox ? These are tests that are targeted\n// directly on top of the language using only the shell API,\n// they are end to end in the sense that they will exercise\n// a lot of different packages on a single test.\n//\n// The objective of these tests is to have a compreensive set\n// of tests that are coupled only the language specification\n// and not to how the language is implemented. These allows\n// extremely aggressive refactorings to be made without\n// incurring in any changes on the tests.\n//\n// There are disadvantages but discussing integration VS unit\n// testing here is not the point (there are also unit tests).\n//\n// Here even tests that involves the script calling syscalls like\n// exit are allowed without interfering with the results of other tests\npackage tests\n"
  },
  {
    "path": "tests/internal/assert/doc.go",
    "content": "// Package assert has some common assert functions\npackage assert\n"
  },
  {
    "path": "tests/internal/assert/equal.go",
    "content": "package assert\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc EqualStrings(t *testing.T, want string, got string) {\n\t// TODO: could use t.Helper here, but only on Go 1.9\n\tif want != got {\n\t\tt.Fatalf(\"wanted[%s] but got[%s]\", want, got)\n\t}\n}\n\nfunc ContainsString(t *testing.T, str string, sub string) {\n\t// TODO: could use t.Helper here, but only on Go 1.9\n\tif !strings.Contains(str, sub) {\n\t\tt.Fatalf(\"[%s] is not a substring of [%s]\", sub, str)\n\t}\n}\n"
  },
  {
    "path": "tests/internal/assert/error.go",
    "content": "package assert\n\nimport \"testing\"\n\nfunc NoError(t *testing.T, err error, operation string) {\n\tif err != nil {\n\t\tt.Fatalf(\"error[%s] %s\", err, operation)\n\t}\n}\n"
  },
  {
    "path": "tests/internal/sh/shell.go",
    "content": "// Package shell makes it easier to run nash scripts for test purposes\npackage sh\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests/internal/assert\"\n)\n\n// Exec runs the script code and returns the result of it.\nfunc Exec(\n\tt *testing.T,\n\tnashpath string,\n\tscriptcode string,\n\tscriptargs ...string,\n) (string, string, error) {\n\tscriptfile, err := ioutil.TempFile(\"\", \"testshell\")\n\tassert.NoError(t, err, \"creating tmp file\")\n\n\tdefer func() {\n\t\terr := scriptfile.Close()\n\t\tassert.NoError(t, err, \"closing tmp file\")\n\t\terr = os.Remove(scriptfile.Name())\n\t\tassert.NoError(t, err, \"deleting tmp file\")\n\t}()\n\n\t_, err = io.Copy(scriptfile, bytes.NewBufferString(scriptcode))\n\tassert.NoError(t, err, \"writing script code to tmp file\")\n\n\tscriptargs = append([]string{scriptfile.Name()}, scriptargs...)\n\tcmd := exec.Command(nashpath, scriptargs...)\n\n\tstdout := bytes.Buffer{}\n\tstderr := bytes.Buffer{}\n\n\tcmd.Stdout = &stdout\n\tcmd.Stderr = &stderr\n\n\terr = cmd.Run()\n\treturn stdout.String(), stderr.String(), err\n}\n"
  },
  {
    "path": "tests/internal/tester/tester.go",
    "content": "// Package tester makes it easy to run multiple\n// script test cases.\npackage tester\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests/internal/assert\"\n\t\"github.com/madlambda/nash/tests/internal/sh\"\n)\n\ntype TestCase struct {\n\tName                  string\n\tScriptCode            string\n\tExpectStdout          string\n\tExpectStderrToContain string\n\tFails                 bool\n}\n\nfunc Run(t *testing.T, nashcmd string, cases ...TestCase) {\n\tfor _, tcase := range cases {\n\t\tt.Run(tcase.Name, func(t *testing.T) {\n\t\t\tstdout, stderr, err := sh.Exec(t, nashcmd, tcase.ScriptCode)\n\t\t\tif !tcase.Fails {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\n\t\t\t\t\t\t\"error[%s] stdout[%s] stderr[%s]\",\n\t\t\t\t\t\terr,\n\t\t\t\t\t\tstdout,\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tif stderr != \"\" {\n\t\t\t\t\tt.Fatalf(\n\t\t\t\t\t\t\"unexpected stderr[%s], on success no stderr is expected\",\n\t\t\t\t\t\tstderr,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tcase.ExpectStdout != \"\" {\n\t\t\t\tassert.EqualStrings(t, tcase.ExpectStdout, stdout)\n\t\t\t}\n\n\t\t\tif tcase.ExpectStderrToContain != \"\" {\n\t\t\t\tassert.ContainsString(t, stderr, tcase.ExpectStderrToContain)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "tests/listindex_test.go",
    "content": "package tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests/internal/tester\"\n)\n\nfunc TestListIndexing(t *testing.T) {\n\ttester.Run(t, Nashcmd,\n\t\ttester.TestCase{\n\t\t\tName: \"PositionalAccess\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = (\"1\" \"2\")\n\t\t\t\techo $a[0]\n\t\t\t\techo $a[1]\n\t\t\t`,\n\t\t\tExpectStdout: \"1\\n2\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"PositionalAssigment\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = (\"1\" \"2\")\n\t\t\t\ta[0] = \"9\"\n\t\t\t\ta[1] = \"p\"\n\t\t\t\techo $a[0] + $a[1]\n\t\t\t`,\n\t\t\tExpectStdout: \"9p\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"PositionalAccessWithVar\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = (\"1\" \"2\")\n\t\t\t\tvar i = \"0\"\n\t\t\t\techo $a[$i]\n\t\t\t\ti = \"1\"\n\t\t\t\techo $a[$i]\n\t\t\t`,\n\t\t\tExpectStdout: \"1\\n2\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"Iteration\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = (\"1\" \"2\" \"3\")\n\t\t\t\tfor x in $a {\n\t\t\t\t\techo $x\n\t\t\t\t}\n\t\t\t`,\n\t\t\tExpectStdout: \"1\\n2\\n3\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IterateEmpty\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = ()\n\t\t\t\tfor x in $a {\n\t\t\t\t\texit(\"1\")\n\t\t\t\t}\n\t\t\t\techo \"ok\"\n\t\t\t`,\n\t\t\tExpectStdout: \"ok\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IndexOutOfRangeFails\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = (\"1\" \"2\" \"3\")\n\t\t\t\techo $a[3]\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IndexEmptyFails\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = ()\n\t\t\t\techo $a[0]\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "tests/stringindex_test.go",
    "content": "package tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/madlambda/nash/tests/internal/tester\"\n)\n\nfunc TestStringIndexing(t *testing.T) {\n\ttester.Run(t, Nashcmd,\n\t\ttester.TestCase{\n\t\t\tName: \"IterateEmpty\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"\"\n\t\t\t\tfor x in $a {\n\t\t\t\t\texit(\"1\")\n\t\t\t\t}\n\t\t\t\techo \"ok\"\n\t\t\t`,\n\t\t\tExpectStdout: \"ok\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IndexEmptyFails\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"\"\n\t\t\t\techo $a[0]\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IsImmutable\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"12\"\n\t\t\t\ta[0] = \"2\"\n\t\t\t\techo $a\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t)\n}\nfunc TestStringIndexingASCII(t *testing.T) {\n\ttester.Run(t, Nashcmd,\n\t\ttester.TestCase{Name: \"PositionalAccess\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"12\"\n\t\t\t\techo $a[0]\n\t\t\t\techo $a[1]\n\t\t\t`,\n\t\t\tExpectStdout: \"1\\n2\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"PositionalAccessReturnsString\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"12\"\n\t\t\t\tvar x = $a[0] + $a[1]\n\t\t\t\techo $x\n\t\t\t`,\n\t\t\tExpectStdout: \"12\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"Len\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"12\"\n\t\t\t\tvar l <= len($a)\n\t\t\t\techo $l\n\t\t\t`,\n\t\t\tExpectStdout: \"2\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"Iterate\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"123\"\n\t\t\t\tfor x in $a {\n\t\t\t\t\techo $x\n\t\t\t\t}\n\t\t\t`,\n\t\t\tExpectStdout: \"1\\n2\\n3\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IndexOutOfRangeFails\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"123\"\n\t\t\t\techo $a[3]\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t)\n}\n\nfunc TestStringIndexingNonASCII(t *testing.T) {\n\ttester.Run(t, Nashcmd,\n\t\ttester.TestCase{Name: \"PositionalAccess\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"⌘⌘\"\n\t\t\t\techo $a[0]\n\t\t\t\techo $a[1]\n\t\t\t`,\n\t\t\tExpectStdout: \"⌘\\n⌘\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"Iterate\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"⌘⌘\"\n\t\t\t\tfor x in $a {\n\t\t\t\t\techo $x\n\t\t\t\t}\n\t\t\t`,\n\t\t\tExpectStdout: \"⌘\\n⌘\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"PositionalAccessReturnsString\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"⌘⌘\"\n\t\t\t\tvar x = $a[0] + $a[1]\n\t\t\t\techo $x\n\t\t\t`,\n\t\t\tExpectStdout: \"⌘⌘\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"Len\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"⌘⌘\"\n\t\t\t\tvar l <= len($a)\n\t\t\t\techo $l\n\t\t\t`,\n\t\t\tExpectStdout: \"2\\n\",\n\t\t},\n\t\ttester.TestCase{\n\t\t\tName: \"IndexOutOfRangeFails\",\n\t\t\tScriptCode: `\n\t\t\t\tvar a = \"⌘⌘\"\n\t\t\t\techo $a[2]\n\t\t\t`,\n\t\t\tFails: true,\n\t\t\tExpectStderrToContain: \"IndexError\",\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "token/token.go",
    "content": "package token\n\nimport \"strconv\"\n\ntype (\n\tToken int\n\n\tFileInfo struct {\n\t\tline, column int\n\t}\n)\n\nconst (\n\tIllegal Token = iota + 1 // error ocurred\n\tEOF\n\tComment\n\n\tliteral_beg\n\n\tIdent\n\tString // \"<string>\"\n\tNumber // [0-9]+\n\tArg\n\n\tliteral_end\n\n\toperator_beg\n\n\tAssign    // =\n\tAssignCmd // <=\n\tEqual     // ==\n\tNotEqual  // !=\n\tPlus      // +\n\tMinus     // -\n\tGt        // >\n\tLt        // <\n\n\tColon     // ,\n\tSemicolon // ;\n\n\toperator_end\n\n\tLBrace // {\n\tRBrace // }\n\tLParen // (\n\tRParen // )\n\tLBrack // [\n\tRBrack // ]\n\tPipe\n\n\tComma\n\tDotdotdot\n\n\tVariable\n\n\tkeyword_beg\n\n\tImport\n\tSetEnv\n\tShowEnv\n\tBindFn // \"bindfn <fn> <cmd>\n\tDump   // \"dump\" [ file ]\n\tReturn\n\tIf\n\tElse\n\tFor\n\tRfork\n\tFn\n\tVar\n\n\tkeyword_end\n)\n\nvar tokens = [...]string{\n\tIllegal: \"ILLEGAL\",\n\tEOF:     \"EOF\",\n\tComment: \"COMMENT\",\n\n\tIdent:  \"IDENT\",\n\tString: \"STRING\",\n\tNumber: \"NUMBER\",\n\tArg:    \"ARG\",\n\n\tAssign:    \"=\",\n\tAssignCmd: \"<=\",\n\tEqual:     \"==\",\n\tNotEqual:  \"!=\",\n\tPlus:      \"+\",\n\tMinus:     \"-\",\n\tGt:        \">\",\n\tLt:        \"<\",\n\n\tColon:     \",\",\n\tSemicolon: \";\",\n\n\tLBrace: \"{\",\n\tRBrace: \"}\",\n\tLParen: \"(\",\n\tRParen: \")\",\n\tLBrack: \"[\",\n\tRBrack: \"]\",\n\tPipe:   \"|\",\n\n\tComma:     \",\",\n\tDotdotdot: \"...\",\n\n\tVariable: \"VARIABLE\",\n\n\tImport:  \"import\",\n\tSetEnv:  \"setenv\",\n\tShowEnv: \"showenv\",\n\tBindFn:  \"bindfn\",\n\tDump:    \"dump\",\n\tReturn:  \"return\",\n\tIf:      \"if\",\n\tElse:    \"else\",\n\tFor:     \"for\",\n\tRfork:   \"rfork\",\n\tFn:      \"fn\",\n\tVar:     \"var\",\n}\n\nvar keywords map[string]Token\n\nfunc init() {\n\tkeywords = make(map[string]Token)\n\tfor i := keyword_beg + 1; i < keyword_end; i++ {\n\t\tkeywords[tokens[i]] = i\n\t}\n}\n\nfunc Lookup(ident string) Token {\n\tif tok, isKeyword := keywords[ident]; isKeyword {\n\t\treturn tok\n\t}\n\n\treturn Ident\n}\n\nfunc IsKeyword(t Token) bool {\n\tif t > keyword_beg && t < keyword_end {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc NewFileInfo(l, c int) FileInfo { return FileInfo{l, c} }\nfunc (info FileInfo) Line() int     { return info.line }\nfunc (info FileInfo) Column() int   { return info.column }\n\nfunc (tok Token) String() string {\n\ts := \"\"\n\n\tif 0 < tok && tok < Token(len(tokens)) {\n\t\ts = tokens[tok]\n\t}\n\tif s == \"\" {\n\t\ts = \"token(\" + strconv.Itoa(int(tok)) + \")\"\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "vendor/golang.org/x/exp/ebnf/ebnf.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package ebnf is a library for EBNF grammars. The input is text ([]byte)\n// satisfying the following grammar (represented itself in EBNF):\n//\n//\tProduction  = name \"=\" [ Expression ] \".\" .\n//\tExpression  = Alternative { \"|\" Alternative } .\n//\tAlternative = Term { Term } .\n//\tTerm        = name | token [ \"…\" token ] | Group | Option | Repetition .\n//\tGroup       = \"(\" Expression \")\" .\n//\tOption      = \"[\" Expression \"]\" .\n//\tRepetition  = \"{\" Expression \"}\" .\n//\n// A name is a Go identifier, a token is a Go string, and comments\n// and white space follow the same rules as for the Go language.\n// Production names starting with an uppercase Unicode letter denote\n// non-terminal productions (i.e., productions which allow white-space\n// and comments between tokens); all other production names denote\n// lexical productions.\n//\npackage ebnf // import \"golang.org/x/exp/ebnf\"\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"text/scanner\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\n// ----------------------------------------------------------------------------\n// Error handling\n\ntype errorList []error\n\nfunc (list errorList) Err() error {\n\tif len(list) == 0 {\n\t\treturn nil\n\t}\n\treturn list\n}\n\nfunc (list errorList) Error() string {\n\tswitch len(list) {\n\tcase 0:\n\t\treturn \"no errors\"\n\tcase 1:\n\t\treturn list[0].Error()\n\t}\n\treturn fmt.Sprintf(\"%s (and %d more errors)\", list[0], len(list)-1)\n}\n\nfunc newError(pos scanner.Position, msg string) error {\n\treturn errors.New(fmt.Sprintf(\"%s: %s\", pos, msg))\n}\n\n// ----------------------------------------------------------------------------\n// Internal representation\n\ntype (\n\t// An Expression node represents a production expression.\n\tExpression interface {\n\t\t// Pos is the position of the first character of the syntactic construct\n\t\tPos() scanner.Position\n\t}\n\n\t// An Alternative node represents a non-empty list of alternative expressions.\n\tAlternative []Expression // x | y | z\n\n\t// A Sequence node represents a non-empty list of sequential expressions.\n\tSequence []Expression // x y z\n\n\t// A Name node represents a production name.\n\tName struct {\n\t\tStringPos scanner.Position\n\t\tString    string\n\t}\n\n\t// A Token node represents a literal.\n\tToken struct {\n\t\tStringPos scanner.Position\n\t\tString    string\n\t}\n\n\t// A List node represents a range of characters.\n\tRange struct {\n\t\tBegin, End *Token // begin ... end\n\t}\n\n\t// A Group node represents a grouped expression.\n\tGroup struct {\n\t\tLparen scanner.Position\n\t\tBody   Expression // (body)\n\t}\n\n\t// An Option node represents an optional expression.\n\tOption struct {\n\t\tLbrack scanner.Position\n\t\tBody   Expression // [body]\n\t}\n\n\t// A Repetition node represents a repeated expression.\n\tRepetition struct {\n\t\tLbrace scanner.Position\n\t\tBody   Expression // {body}\n\t}\n\n\t// A Production node represents an EBNF production.\n\tProduction struct {\n\t\tName *Name\n\t\tExpr Expression\n\t}\n\n\t// A Bad node stands for pieces of source code that lead to a parse error.\n\tBad struct {\n\t\tTokPos scanner.Position\n\t\tError  string // parser error message\n\t}\n\n\t// A Grammar is a set of EBNF productions. The map\n\t// is indexed by production name.\n\t//\n\tGrammar map[string]*Production\n)\n\nfunc (x Alternative) Pos() scanner.Position { return x[0].Pos() } // the parser always generates non-empty Alternative\nfunc (x Sequence) Pos() scanner.Position    { return x[0].Pos() } // the parser always generates non-empty Sequences\nfunc (x *Name) Pos() scanner.Position       { return x.StringPos }\nfunc (x *Token) Pos() scanner.Position      { return x.StringPos }\nfunc (x *Range) Pos() scanner.Position      { return x.Begin.Pos() }\nfunc (x *Group) Pos() scanner.Position      { return x.Lparen }\nfunc (x *Option) Pos() scanner.Position     { return x.Lbrack }\nfunc (x *Repetition) Pos() scanner.Position { return x.Lbrace }\nfunc (x *Production) Pos() scanner.Position { return x.Name.Pos() }\nfunc (x *Bad) Pos() scanner.Position        { return x.TokPos }\n\n// ----------------------------------------------------------------------------\n// Grammar verification\n\nfunc isLexical(name string) bool {\n\tch, _ := utf8.DecodeRuneInString(name)\n\treturn !unicode.IsUpper(ch)\n}\n\ntype verifier struct {\n\terrors   errorList\n\tworklist []*Production\n\treached  Grammar // set of productions reached from (and including) the root production\n\tgrammar  Grammar\n}\n\nfunc (v *verifier) error(pos scanner.Position, msg string) {\n\tv.errors = append(v.errors, newError(pos, msg))\n}\n\nfunc (v *verifier) push(prod *Production) {\n\tname := prod.Name.String\n\tif _, found := v.reached[name]; !found {\n\t\tv.worklist = append(v.worklist, prod)\n\t\tv.reached[name] = prod\n\t}\n}\n\nfunc (v *verifier) verifyChar(x *Token) rune {\n\ts := x.String\n\tif utf8.RuneCountInString(s) != 1 {\n\t\tv.error(x.Pos(), \"single char expected, found \"+s)\n\t\treturn 0\n\t}\n\tch, _ := utf8.DecodeRuneInString(s)\n\treturn ch\n}\n\nfunc (v *verifier) verifyExpr(expr Expression, lexical bool) {\n\tswitch x := expr.(type) {\n\tcase nil:\n\t\t// empty expression\n\tcase Alternative:\n\t\tfor _, e := range x {\n\t\t\tv.verifyExpr(e, lexical)\n\t\t}\n\tcase Sequence:\n\t\tfor _, e := range x {\n\t\t\tv.verifyExpr(e, lexical)\n\t\t}\n\tcase *Name:\n\t\t// a production with this name must exist;\n\t\t// add it to the worklist if not yet processed\n\t\tif prod, found := v.grammar[x.String]; found {\n\t\t\tv.push(prod)\n\t\t} else {\n\t\t\tv.error(x.Pos(), \"missing production \"+x.String)\n\t\t}\n\t\t// within a lexical production references\n\t\t// to non-lexical productions are invalid\n\t\tif lexical && !isLexical(x.String) {\n\t\t\tv.error(x.Pos(), \"reference to non-lexical production \"+x.String)\n\t\t}\n\tcase *Token:\n\t\t// nothing to do for now\n\tcase *Range:\n\t\ti := v.verifyChar(x.Begin)\n\t\tj := v.verifyChar(x.End)\n\t\tif i >= j {\n\t\t\tv.error(x.Pos(), \"decreasing character range\")\n\t\t}\n\tcase *Group:\n\t\tv.verifyExpr(x.Body, lexical)\n\tcase *Option:\n\t\tv.verifyExpr(x.Body, lexical)\n\tcase *Repetition:\n\t\tv.verifyExpr(x.Body, lexical)\n\tcase *Bad:\n\t\tv.error(x.Pos(), x.Error)\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"internal error: unexpected type %T\", expr))\n\t}\n}\n\nfunc (v *verifier) verify(grammar Grammar, start string) {\n\t// find root production\n\troot, found := grammar[start]\n\tif !found {\n\t\tvar noPos scanner.Position\n\t\tv.error(noPos, \"no start production \"+start)\n\t\treturn\n\t}\n\n\t// initialize verifier\n\tv.worklist = v.worklist[0:0]\n\tv.reached = make(Grammar)\n\tv.grammar = grammar\n\n\t// work through the worklist\n\tv.push(root)\n\tfor {\n\t\tn := len(v.worklist) - 1\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tprod := v.worklist[n]\n\t\tv.worklist = v.worklist[0:n]\n\t\tv.verifyExpr(prod.Expr, isLexical(prod.Name.String))\n\t}\n\n\t// check if all productions were reached\n\tif len(v.reached) < len(v.grammar) {\n\t\tfor name, prod := range v.grammar {\n\t\t\tif _, found := v.reached[name]; !found {\n\t\t\t\tv.error(prod.Pos(), name+\" is unreachable\")\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Verify checks that:\n//\t- all productions used are defined\n//\t- all productions defined are used when beginning at start\n//\t- lexical productions refer only to other lexical productions\n//\n// Position information is interpreted relative to the file set fset.\n//\nfunc Verify(grammar Grammar, start string) error {\n\tvar v verifier\n\tv.verify(grammar, start)\n\treturn v.errors.Err()\n}\n"
  },
  {
    "path": "vendor/golang.org/x/exp/ebnf/parser.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage ebnf\n\nimport (\n\t\"io\"\n\t\"strconv\"\n\t\"text/scanner\"\n)\n\ntype parser struct {\n\terrors  errorList\n\tscanner scanner.Scanner\n\tpos     scanner.Position // token position\n\ttok     rune             // one token look-ahead\n\tlit     string           // token literal\n}\n\nfunc (p *parser) next() {\n\tp.tok = p.scanner.Scan()\n\tp.pos = p.scanner.Position\n\tp.lit = p.scanner.TokenText()\n}\n\nfunc (p *parser) error(pos scanner.Position, msg string) {\n\tp.errors = append(p.errors, newError(pos, msg))\n}\n\nfunc (p *parser) errorExpected(pos scanner.Position, msg string) {\n\tmsg = `expected \"` + msg + `\"`\n\tif pos.Offset == p.pos.Offset {\n\t\t// the error happened at the current position;\n\t\t// make the error message more specific\n\t\tmsg += \", found \" + scanner.TokenString(p.tok)\n\t\tif p.tok < 0 {\n\t\t\tmsg += \" \" + p.lit\n\t\t}\n\t}\n\tp.error(pos, msg)\n}\n\nfunc (p *parser) expect(tok rune) scanner.Position {\n\tpos := p.pos\n\tif p.tok != tok {\n\t\tp.errorExpected(pos, scanner.TokenString(tok))\n\t}\n\tp.next() // make progress in any case\n\treturn pos\n}\n\nfunc (p *parser) parseIdentifier() *Name {\n\tpos := p.pos\n\tname := p.lit\n\tp.expect(scanner.Ident)\n\treturn &Name{pos, name}\n}\n\nfunc (p *parser) parseToken() *Token {\n\tpos := p.pos\n\tvalue := \"\"\n\tif p.tok == scanner.String {\n\t\tvalue, _ = strconv.Unquote(p.lit)\n\t\t// Unquote may fail with an error, but only if the scanner found\n\t\t// an illegal string in the first place. In this case the error\n\t\t// has already been reported.\n\t\tp.next()\n\t} else {\n\t\tp.expect(scanner.String)\n\t}\n\treturn &Token{pos, value}\n}\n\n// ParseTerm returns nil if no term was found.\nfunc (p *parser) parseTerm() (x Expression) {\n\tpos := p.pos\n\n\tswitch p.tok {\n\tcase scanner.Ident:\n\t\tx = p.parseIdentifier()\n\n\tcase scanner.String:\n\t\ttok := p.parseToken()\n\t\tx = tok\n\t\tconst ellipsis = '…' // U+2026, the horizontal ellipsis character\n\t\tif p.tok == ellipsis {\n\t\t\tp.next()\n\t\t\tx = &Range{tok, p.parseToken()}\n\t\t}\n\n\tcase '(':\n\t\tp.next()\n\t\tx = &Group{pos, p.parseExpression()}\n\t\tp.expect(')')\n\n\tcase '[':\n\t\tp.next()\n\t\tx = &Option{pos, p.parseExpression()}\n\t\tp.expect(']')\n\n\tcase '{':\n\t\tp.next()\n\t\tx = &Repetition{pos, p.parseExpression()}\n\t\tp.expect('}')\n\t}\n\n\treturn x\n}\n\nfunc (p *parser) parseSequence() Expression {\n\tvar list Sequence\n\n\tfor x := p.parseTerm(); x != nil; x = p.parseTerm() {\n\t\tlist = append(list, x)\n\t}\n\n\t// no need for a sequence if list.Len() < 2\n\tswitch len(list) {\n\tcase 0:\n\t\tp.errorExpected(p.pos, \"term\")\n\t\treturn &Bad{p.pos, \"term expected\"}\n\tcase 1:\n\t\treturn list[0]\n\t}\n\n\treturn list\n}\n\nfunc (p *parser) parseExpression() Expression {\n\tvar list Alternative\n\n\tfor {\n\t\tlist = append(list, p.parseSequence())\n\t\tif p.tok != '|' {\n\t\t\tbreak\n\t\t}\n\t\tp.next()\n\t}\n\t// len(list) > 0\n\n\t// no need for an Alternative node if list.Len() < 2\n\tif len(list) == 1 {\n\t\treturn list[0]\n\t}\n\n\treturn list\n}\n\nfunc (p *parser) parseProduction() *Production {\n\tname := p.parseIdentifier()\n\tp.expect('=')\n\tvar expr Expression\n\tif p.tok != '.' {\n\t\texpr = p.parseExpression()\n\t}\n\tp.expect('.')\n\treturn &Production{name, expr}\n}\n\nfunc (p *parser) parse(filename string, src io.Reader) Grammar {\n\tp.scanner.Init(src)\n\tp.scanner.Filename = filename\n\tp.next() // initializes pos, tok, lit\n\n\tgrammar := make(Grammar)\n\tfor p.tok != scanner.EOF {\n\t\tprod := p.parseProduction()\n\t\tname := prod.Name.String\n\t\tif _, found := grammar[name]; !found {\n\t\t\tgrammar[name] = prod\n\t\t} else {\n\t\t\tp.error(prod.Pos(), name+\" declared already\")\n\t\t}\n\t}\n\n\treturn grammar\n}\n\n// Parse parses a set of EBNF productions from source src.\n// It returns a set of productions. Errors are reported\n// for incorrect syntax and if a production is declared\n// more than once; the filename is used only for error\n// positions.\n//\nfunc Parse(filename string, src io.Reader) (Grammar, error) {\n\tvar p parser\n\tgrammar := p.parse(filename, src)\n\treturn grammar, p.errors.Err()\n}\n"
  }
]