diff --git a/README.md b/README.md index 77b10e8..49939ff 100644 --- a/README.md +++ b/README.md @@ -154,20 +154,25 @@ for i := 0; i < len(s); i++ { } ``` -### Programs +### Programs/Scripts ```golang -// Programs are called by stating the program name preceded by an @. -@ls("-a") +// Programs/Scripts are called by stating the name preceded by an @. +@dir("/b") ``` ```golang -// Similar to Bash/Batch, the output can be piped into another program. -@ls("-a") | @sort() +// Similar to Bash/Batch, the output can be piped into another program/script. +@dir("/b") | @sort("/r") ``` ```golang // To capture the output, just assign the call chain to variables. -stdout, stderr, code := @ls("-a") | @sort() +stdout, stderr, code := @dir("/b") | @sort("/r") +``` + +```golang +// To specify the path to a program/script, a string literal is used. +@`helper\dir.bat`("/b") // Equivalent to @"helper\\dir.bat"("/b") ``` ### Imports diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 93426cc..2d11ebc 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -85,8 +85,13 @@ func forLabel(count int) string { func (c *converter) StringToString(value string) string { c.addLf() + // Escape all "!". + value = strings.ReplaceAll(value, "!", "^!") + // Replace "\n" with Batch newline value. - return strings.ReplaceAll(value, "\n", "!LF!") + value = strings.ReplaceAll(value, "\n", "!LF!") + + return value } func (c *converter) Dump() (string, error) { diff --git a/main.go b/main.go deleted file mode 100644 index 92bab69..0000000 --- a/main.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/monstermichl/typeshell/converters/bash" - "github.com/monstermichl/typeshell/converters/batch" - "github.com/monstermichl/typeshell/transpiler" -) - -const ( - typeBatch string = "batch" - typeBash string = "bash" -) - -var convMapping = map[string]transpiler.Converter{ - typeBatch: batch.New(), - typeBash: bash.New(), -} - -type options struct { - in string - out string - converters []transpiler.Converter -} - -func parseOptions() options { - args := os.Args - options := options{} - types := []string{} - - for k := range convMapping { - types = append(types, k) - } - - for i := 1; i < (len(args) - 1); i += 2 { - cSwitch := args[i] - cValue := args[i+1] - - switch cSwitch { - case "-i", "--in": - // Make sure input file exists. - if stat, err := os.Stat(cValue); err != nil { - panic(fmt.Errorf("input file %s doesn't exist", cValue)) - } else if stat.IsDir() { - panic(fmt.Errorf("input %s is not a file", cValue)) - } - options.in = cValue - case "-o", "--out": - // Make sure output path exists. - if stat, err := os.Stat(cValue); err != nil { - panic(fmt.Errorf("output path %s doesn't exist", cValue)) - } else if !stat.IsDir() { - panic(fmt.Errorf("output path %s is not a directory", cValue)) - } - options.out = cValue - case "-t", "--type": - conv, ok := convMapping[cValue] - - if !ok { - panic(fmt.Errorf("unknown converter type %s. Allowed types are %s", cValue, strings.Join(types, ", "))) - } - options.converters = append(options.converters, conv) - default: - panic(fmt.Errorf("unknown option %s", cSwitch)) - } - } - - if len(options.in) == 0 { - panic("no input file provided (-i/--in)") - } else if len(options.out) == 0 { - panic("no output directory provided (-o/--out)") - } else if len(options.converters) == 0 { - panic("no output type provided (-t/--type)") - } - return options -} - -func main() { - options := parseOptions() - t := transpiler.New() - - for _, conv := range options.converters { - in := options.in - dump, err := t.Transpile(in, conv) - - if err != nil { - panic(err) - } - file := filepath.Base(in) - file = file[0 : len(file)-len(filepath.Ext(in))] // Remove extension. - - os.WriteFile(filepath.Join(options.out, fmt.Sprintf("%s.%s", file, conv.Extension())), []byte(dump), 0777) - } -} diff --git a/parser/parser.go b/parser/parser.go index 5db172e..a816fd1 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1076,7 +1076,16 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { // Check if the amount of values is equal to the amount of variable names. if valuesTypesLen != variablesLen { - return nil, p.atError(fmt.Sprintf("got %d initialisation values but %d variables", valuesTypesLen, variablesLen), nextToken) + pluralInit := "" + pluralValues := "" + + if valuesTypesLen != 1 { + pluralInit = "s" + } + if variablesLen != 1 { + pluralValues = "s" + } + return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d variable%s", valuesTypesLen, pluralInit, variablesLen, pluralValues), nextToken) } // If a type has been specified, make sure the returned types fit this type. @@ -1355,7 +1364,7 @@ func (p *Parser) evaluateFunctionDefinition(ctx context) (Statement, error) { closingBrace := p.eat() if closingBrace.Type() != lexer.CLOSING_ROUND_BRACKET { - return nil, p.expectedError("closing bracket", closingBrace) + return nil, p.expectedError(`")"`, closingBrace) } } returnTypeToken := p.peek() @@ -1994,7 +2003,7 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { closingToken := p.eat() if closingToken.Type() != lexer.CLOSING_ROUND_BRACKET { - return nil, p.expectedError("closing bracket", closingToken) + return nil, p.expectedError(`")"`, closingToken) } // Handle slice instantiation. @@ -2300,7 +2309,7 @@ func (p *Parser) evaluateArguments(typeName string, name string, params []Variab openingBraceToken := p.eat() if openingBraceToken.Type() != lexer.OPENING_ROUND_BRACKET { - return nil, p.expectedError("opening bracket", openingBraceToken) + return nil, p.expectedError(`"("`, openingBraceToken) } nextToken := p.peek() args := []Expression{} @@ -2347,7 +2356,7 @@ func (p *Parser) evaluateArguments(typeName string, name string, params []Variab tokenType := nextToken.Type() if !slices.Contains([]lexer.TokenType{lexer.COMMA, lexer.CLOSING_ROUND_BRACKET}, tokenType) { - err = p.expectedError("comma or closing bracket", nextToken) + err = p.expectedError(`"," or ")"`, nextToken) break } else if tokenType == lexer.COMMA { p.eat() @@ -2370,7 +2379,7 @@ func (p *Parser) evaluateArguments(typeName string, name string, params []Variab closingBraceToken := p.eat() if closingBraceToken.Type() != lexer.CLOSING_ROUND_BRACKET { - return nil, p.expectedError("closing bracket", closingBraceToken) + return nil, p.expectedError(`")"`, closingBraceToken) } return args, nil } @@ -2438,8 +2447,11 @@ func (p *Parser) evaluateAppCall(ctx context) (Call, error) { nextToken = p.eat() name := nextToken.Value() - if nextToken.Type() != lexer.IDENTIFIER { - return nil, p.expectedError("program identifier", nextToken) + switch nextToken.Type() { + case lexer.IDENTIFIER, lexer.STRING_LITERAL: + // Nothing to do, those cases are valid. + default: + return nil, p.expectedError("program identifier or string literal", nextToken) } args, err := p.evaluateArguments("program", name, nil, ctx) diff --git a/tests/appcall_linux_test.go b/tests/appcall_linux_test.go index 991447c..94a05c3 100644 --- a/tests/appcall_linux_test.go +++ b/tests/appcall_linux_test.go @@ -2,6 +2,8 @@ package tests import ( "fmt" + "os" + "path" "testing" "github.com/stretchr/testify/require" @@ -33,6 +35,31 @@ func TestLsCallPipeToGrepCallSuccess(t *testing.T) { }) } +func TestBashFileFromSubDirCallCallPipeToGrepCallSuccess(t *testing.T) { + transpileBashFunc(t, func(dir string) (string, error) { + subdir := path.Join(dir, "subdir") + err := os.Mkdir(subdir, 0700) + + if err != nil { + return "", err + } + bashFile := path.Join(subdir, "bash.sh") + err = os.WriteFile(bashFile, []byte("ls $1"), 0700) + + if err != nil { + return "", err + } + return ` + ` + fmt.Sprintf(`var stdout, stderr, code = @"%s"("%s") | @grep(".tsh")`, bashFile, dir) + ` + + print(stdout, code) + `, nil + }, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "test.tsh 0", output) + }) +} + func TestLsCallFail(t *testing.T) { transpileBashFunc(t, func(dir string) (string, error) { return ` diff --git a/tests/appcall_windows_test.go b/tests/appcall_windows_test.go index 0c02000..16d6490 100644 --- a/tests/appcall_windows_test.go +++ b/tests/appcall_windows_test.go @@ -2,6 +2,8 @@ package tests import ( "fmt" + "os" + "path" "strings" "testing" @@ -34,6 +36,31 @@ func TestDirCallPipeToFindstrCallSuccess(t *testing.T) { }) } +func TestBatFileFromSubDirCallPipeToFindstrCallSuccess(t *testing.T) { + transpileBatchFunc(t, func(dir string) (string, error) { + subdir := path.Join(dir, "subdir") + err := os.Mkdir(subdir, 0700) + + if err != nil { + return "", err + } + batFile := path.Join(subdir, "bat.bat") + err = os.WriteFile(batFile, []byte("dir /b %1"), 0700) + + if err != nil { + return "", err + } + return ` + ` + fmt.Sprintf(`var stdout, stderr, code = @"%s"("%s") | @findstr(".tsh")`, strings.ReplaceAll(strings.ReplaceAll(batFile, `/`, `\`), `\`, `\\`), strings.ReplaceAll(dir, `\`, `\\`)) + ` + + print(stdout, code) + `, nil + }, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "test.tsh 0", output) + }) +} + func TestDirCallFail(t *testing.T) { transpileBatchFunc(t, func(dir string) (string, error) { return ` diff --git a/tests/builtin.go b/tests/builtin.go index 807542b..948b246 100644 --- a/tests/builtin.go +++ b/tests/builtin.go @@ -49,7 +49,7 @@ func testCopySuccess(t *testing.T, transpilerFunc transpilerFunc) { func testReadSuccess(t *testing.T, transpilerFunc transpilerFunc) { file := "read-test.txt" content := "Hello World" - os.WriteFile(file, []byte(content), 0x777) + os.WriteFile(file, []byte(content), 0700) defer os.Remove(file) transpilerFunc(t, ` @@ -156,7 +156,7 @@ func testCopyInFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { func testReadInFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { file := "read-test.txt" content := "Hello World" - os.WriteFile(file, []byte(content), 0x777) + os.WriteFile(file, []byte(content), 0700) defer os.Remove(file) transpilerFunc(t, ` diff --git a/tests/helpers.go b/tests/helpers.go index 4d435d6..74c24c4 100644 --- a/tests/helpers.go +++ b/tests/helpers.go @@ -33,7 +33,7 @@ func transpileFunc(t *testing.T, source sourceCallout, targetFileName string, co if err == nil { var code string - err = os.WriteFile(file, []byte(src), 0x777) + err = os.WriteFile(file, []byte(src), 0700) require.Nil(t, err) code, err = trans.Transpile(file, converter) @@ -42,7 +42,7 @@ func transpileFunc(t *testing.T, source sourceCallout, targetFileName string, co // If transpilation was successful, run the code. if err == nil { targetFile := filepath.Join(dir, targetFileName) - err = os.WriteFile(targetFile, []byte(code), 0x777) + err = os.WriteFile(targetFile, []byte(code), 0700) require.Nil(t, err) cmd := exec.Command(targetFile)