Compare commits

..

15 Commits

Author SHA1 Message Date
31c3a2f3a9 Add ability to define functions with dashes in name 2021-04-29 23:14:52 -07:00
836149ffc2 Add goroutines 2021-04-05 11:28:47 -07:00
e5b8521ba4 Add syntax highlighting file 2021-03-24 10:54:10 -07:00
8aa6f45db2 Add defer file.Close() to the README interpreter example 2021-03-08 11:35:15 -08:00
ecfbd40df8 Remove unnecessary copyright headers 2021-03-08 11:28:47 -08:00
bb89c93344 Add opposite values and README.md 2021-03-08 11:21:50 -08:00
36d1eee759 Move default maps/variables to default.go and add print default function 2021-03-07 19:01:43 -08:00
1c99345af1 Implement return, implement instant break, add exit function, implement dumping and loading of AST via encoding/json 2021-03-07 17:25:16 -08:00
f7a34b3da4 Fix issue where function call within array was not executedD 2021-03-05 19:38:05 -08:00
53e0717b91 Implement functions, arrays, maps, and while loops. Document and clean up code. 2021-03-04 19:30:08 -08:00
201030ed93 Add conversion functions and move other defaults to cmd/scpt 2021-03-03 00:32:00 -08:00
e6d195f364 Add conversion functions and move other defaults to cmd/scpt 2021-03-03 00:27:54 -08:00
ba11fdcf76 Add example for adding functions to cmd/scpt 2021-03-02 00:33:55 -08:00
1196942801 Clean up ParseValue() 2021-03-02 00:27:28 -08:00
412079c20a Remove index variable after loop completion 2021-03-02 00:18:52 -08:00
11 changed files with 897 additions and 306 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
scpt
/scpt
.idea/

81
README.md Normal file
View File

@@ -0,0 +1,81 @@
# scpt
scpt is an applescript-inspired scripting language written for fun and to see if I could.
[![Go Reference](https://pkg.go.dev/badge/gitea.arsenm.dev/Arsen6331/scpt.svg)](https://pkg.go.dev/gitea.arsenm.dev/Arsen6331/scpt)
---
### Usage
scpt is to be used as a library imported into Go. A basic interpreter with no extra functionality would look like this:
```go
package main
import (
"gitea.arsenm.dev/Arsen6331/scpt"
"log"
"os"
"path/filepath"
)
func main() {
filename := os.Args[1]
file, err := os.Open(filepath.Clean(filename))
if err != nil {
log.Fatalln(err)
}
defer file.Close()
ast, err := scpt.Parse(file)
if err != nil {
log.Fatalln(err)
}
err = ast.Execute()
if err != nil {
log.Fatalln(err)
}
}
```
---
### Basic Syntax
The basic syntax of scpt can be learned from the test.scpt file.
---
### Default Functions
scpt comes with the following default functions:
- `str`: Convert value to string
- `num`: Parse string to number (`float64`)
- `bool`: Parse string to boolean
- `break`: Break out of loop (Errors if not in loop)
- `append`: Return an array with given items appended
- `exit`: Exit with given exit code
- `return`: Return value in function (Errors if not within function)
- `print`: Print using `fmt.Println()`
---
### Adding functionality:
Adding functionality is simple and requires a call to `scpt.AddFuncs()` or `scpt.AddVars()`. Here are some examples:
```go
scpt.AddFuncs(scpt.FuncMap{
"my-function": myFunction
})
```
Where `myFunction` is:
```go
func myFunction(args map[string]interface{}) (interface{}, error) {
fmt.Println(args)
return nil, nil
}
```
After the call to `scpt.AddFuncs()`, `my-function` can be used to run the function from within an scpt script. Variables work similarly.

448
ast.go
View File

@@ -1,25 +1,18 @@
/*
Copyright (c) 2021 Arsen Musayelyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
package scpt
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/alecthomas/participle/lexer"
)
var loopRunning bool
var breakLoop bool
var funcRunning bool
var retValue interface{} = nil
// AST stores the root of the Abstract Syntax Tree for scpt
type AST struct {
Pos lexer.Position
@@ -41,29 +34,229 @@ func (ast *AST) Execute() error {
return nil
}
func (ast *AST) Dump() ([]byte, error) {
return json.Marshal(ast)
}
func (ast *AST) DumpPretty() ([]byte, error) {
buf := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(buf)
enc.SetIndent("", " ")
err := enc.Encode(ast)
if err != nil {
return buf.Bytes(), err
}
return buf.Bytes(), nil
}
func LoadAST(data []byte) (*AST, error) {
var ast AST
err := json.Unmarshal(data, &ast)
if err != nil {
return nil, err
}
return &ast, nil
}
// Execute a variable declaration
func executeVarCmd(Var *Var) error {
// Parse value of variable
val, err := ParseValue(Var.Value)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
// If value of variable is a function call
if IsFuncCall(val) {
// Assert type of val as *FuncCall
Call := val.(*FuncCall)
// Set variable value to function return value
Vars[Var.Key], err = CallFunction(Call)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
} else if Var.Index != nil {
// If variable definition has an associated index, get index value
index, err := callIfFunc(ParseValue(Var.Index))
if err != nil {
return fmt.Errorf("%s: %s", Var.Index.Pos, err)
}
// Attempt to get the variable from Vars and assert it as a []interface{}
slc, ok := Vars[Var.Key].([]interface{})
// If assertion successful
if ok {
// Assert index value as a 64-bit float
indexInt, ok := index.(float64)
if !ok {
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not an array")
}
// Set integer index of interface{} slice to value
slc[int64(indexInt)] = val
} else {
// If slice assertion unsuccessful, attempt to assert as map[interface{}]interface{}
iMap, ok := Vars[Var.Key].(map[interface{}]interface{})
if !ok {
return fmt.Errorf("%s: %s", Var.Pos, "variable "+Var.Key+" does not exist or is not a map")
}
// Set index of interface{} to interface{} map to value
iMap[index] = val
}
} else {
// If value is not a function call, set variable to parsed value
Vars[Var.Key] = val
}
return nil
}
// Execute an if statement
func executeIfCmd(If *If) error {
// Get condition value
condVal, err := callIfFunc(ParseValue(If.Condition))
if err != nil {
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
}
// Attempt to assert condition type as bool
condBool, ok := condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
// If condition is true
if condBool {
// For each inner command
for _, InnerCmd := range If.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
}
}
return nil
}
// Execute a repeat loop
func executeRptLoop(rptLoop *RptLoop) error {
// Set loopRunning to true to allow break
loopRunning = true
// Run for loop with correct amount of iterations
rpt:
for i := 0; i < *rptLoop.Times; i++ {
// If user requested index variable via "{ var in ... }"
if rptLoop.IndexVar != nil {
// Set requested variable name to index
Vars[*rptLoop.IndexVar] = i
}
// For each command within the loop
for _, InnerCmd := range rptLoop.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
// If breakLoop set to true
if breakLoop {
// Reset breakLoop
breakLoop = false
break rpt
}
}
}
// Remove index variable if existent
delete(Vars, *rptLoop.IndexVar)
// Reset loopRunning
loopRunning = false
return nil
}
// Execute a while loop
func executeWhlLoop(whlLoop *WhlLoop) error {
loopRunning = true
// Get condition value
condVal, err := callIfFunc(ParseValue(whlLoop.Condition))
if err != nil {
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
}
// Attempt to assert condition type as bool
condBool, ok := condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
// Run for loop if condition is true
whl:
for condBool {
// For each inner command
for _, InnerCmd := range whlLoop.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
// If breakLoop set to true
if breakLoop {
// Reset breakLoop
breakLoop = false
break whl
}
// Get condition value
condVal, err = callIfFunc(ParseValue(whlLoop.Condition))
if err != nil {
return fmt.Errorf("%s: %s", whlLoop.Condition.Pos, err)
}
// Attempt to assert condition type as bool and update its value
condBool, ok = condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
}
}
loopRunning = false
return nil
}
// Execute a function definition
func executeFuncDef(def *FuncDef) error {
// Set requested function name in Funcs
Funcs[*def.Name] = func(args map[string]interface{}) (interface{}, error) {
funcRunning = true
// Create new empty map[interface{}]interface{}
argIMap := map[interface{}]interface{}{}
// Convert args map[string]interface{} to map[interface{}]interface{}
for key, value := range args {
argIMap[key] = value
}
// Set variable _args to the args map[interface{}]interface{}
Vars["_args"] = argIMap
// For each command within the definition
for _, InnerCmd := range def.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return nil, fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
if retValue != nil {
ret := retValue
retValue = nil
funcRunning = false
return ret, nil
}
}
// Remove args variable from Vars
delete(Vars, "_args")
funcRunning = false
return nil, nil
}
return nil
}
// Parse and execute command
func executeCmd(cmd *Command) error {
// If parsing variables
if cmd.Vars != nil {
// For each variable
for _, Var := range cmd.Vars {
// Parse value of variable
val, err := ParseValue(Var.Value)
// Attempt to execute the variable command
err := executeVarCmd(Var)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
// If value of variable is a function call
if IsFuncCall(val) {
// Assert type of val as *FuncCall
Call := val.(*FuncCall)
// Set variable value to function return value
Vars[Var.Key], err = CallFunction(Call)
if err != nil {
return fmt.Errorf("%s: %s", Var.Value.Pos, err)
}
} else {
// If value is not a function call, set variable value to parsed value
Vars[Var.Key] = val
return err
}
}
} else if cmd.Calls != nil {
@@ -75,45 +268,46 @@ func executeCmd(cmd *Command) error {
return fmt.Errorf("%s: %s", Call.Pos, err)
}
}
} else if cmd.Goroutines != nil {
// For each function call
for _, goroutine := range cmd.Goroutines {
// Attempt to call function
go CallFunction(goroutine.Call)
}
} else if cmd.Ifs != nil {
// For each if statement
for _, If := range cmd.Ifs {
// Get condition value
condVal, err := callIfFunc(ParseValue(If.Condition))
// Attempt to execute the if command
err := executeIfCmd(If)
if err != nil {
return fmt.Errorf("%s: %s", If.Condition.Pos, err)
}
// Attempt to assert condition type as bool
condBool, ok := condVal.(bool)
if !ok {
return errors.New("condition must be a boolean")
}
// If condition is true
if condBool {
// For each inner command
for _, InnerCmd := range If.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
}
return err
}
}
} else if cmd.RptLoops != nil {
// For each repeat loop
for _, RptLoop := range cmd.RptLoops {
for i:=0;i<*RptLoop.Times;i++ {
if RptLoop.IndexVar != nil {
Vars[*RptLoop.IndexVar] = i
}
for _, InnerCmd := range RptLoop.InnerCmds {
// Execute command recursively
err := executeCmd(InnerCmd)
if err != nil {
return fmt.Errorf("%s: %s", InnerCmd.Pos, err)
}
}
// Attempt to execute the repeat loop
err := executeRptLoop(RptLoop)
if err != nil {
return err
}
}
} else if cmd.WhlLoops != nil {
// For each while loop
for _, WhlLoop := range cmd.WhlLoops {
// Attempt to execute the while loop
err := executeWhlLoop(WhlLoop)
if err != nil {
return err
}
}
} else if cmd.Defs != nil {
// For each function definition
for _, Def := range cmd.Defs {
// Attempt to execute the function definition
err := executeFuncDef(Def)
if err != nil {
return err
}
}
}
@@ -122,58 +316,28 @@ func executeCmd(cmd *Command) error {
// Command stores any commands encountered while parsing a script
type Command struct {
Pos lexer.Position
Tokens []lexer.Token
Vars []*Var `( @@`
Ifs []*If `| @@`
RptLoops []*RptLoop`| @@`
Calls []*FuncCall `| @@)`
}
// If stores any if statements encountered while parsing a script
type If struct {
Pos lexer.Position
Condition *Value `"if" @@ "{"`
InnerCmds []*Command `@@* "}"`
}
type RptLoop struct {
Pos lexer.Position
Times *int `"repeat" @Number "times" "{"`
IndexVar *string `(@Ident "in")?`
InnerCmds []*Command `@@* "}"`
}
// FuncCall stores any function calls encountered while parsing a script
type FuncCall struct {
Pos lexer.Position
Name string `@Ident @("-" Ident)*`
Args []*Arg `@@*`
}
// Arg stores arguments for function calls
type Arg struct {
Pos lexer.Position
Key string `("with" @Ident)?`
Value *Value `@@`
}
// Var stores any variables encountered while parsing a script
type Var struct {
Pos lexer.Position
Key string `"set" @Ident "to"`
Value *Value `@@`
Pos lexer.Position
Vars []*Var `( @@`
Ifs []*If `| @@`
RptLoops []*RptLoop `| @@`
WhlLoops []*WhlLoop `| @@`
Defs []*FuncDef `| @@`
Goroutines []*Goroutine `| @@`
Calls []*FuncCall `| @@ )`
}
// Value stores any literal values encountered while parsing a script
type Value struct {
Pos lexer.Position
String *string ` @String`
Number *float64 `| @Number`
Bool *Bool `| @("true" | "false")`
SubCmd *FuncCall `| "(" @@ ")"`
VarVal *string `| "$" @Ident`
Expr *Expression `| "{" @@ "}"`
Pos lexer.Position
String *string ` @String`
Number *float64 `| @Number`
Bool *Bool `| @("true" | "false")`
SubCmd *FuncCall `| "(" @@ ")"`
VarVal *VarVal `| @@`
Expr *Expression `| "{" @@ "}"`
Map []*MapKVPair `| "[" (@@ ("," @@)* )? "]"`
Array []*Value `| "[" (@@ ("," @@)* )? "]"`
Opposite *Value `| "!" @@`
}
// Bool stores boolean values encountered while parsing a script.
@@ -188,6 +352,31 @@ func (b *Bool) Capture(values []string) error {
return nil
}
// FuncCall stores any function calls encountered while parsing a script
type FuncCall struct {
Pos lexer.Position
Name string `@Ident @("-" Ident)*`
Args []*Arg `@@*`
}
type Goroutine struct {
Pos lexer.Position
Call *FuncCall `"go" @@`
}
// Arg stores arguments for function calls
type Arg struct {
Pos lexer.Position
Key string `("with" @Ident)?`
Value *Value `@@`
}
// VarVal stores any references to a variable encountered while parsing a script
type VarVal struct {
Name *string `"$" @Ident`
Index *Value `("[" @@ "]")?`
}
// Expression stores any expressions encountered while parsing a
// script for later evaluation
type Expression struct {
@@ -201,3 +390,46 @@ type ExprRightSeg struct {
Op string `@Operator`
Right *Value `@@`
}
// MapKVPair stores any key/value pairs encountered while parsing map literals
type MapKVPair struct {
Key *Value `@@`
Value *Value `":" @@`
}
// FuncDef stores any function definitions encountered while parsing a script
type FuncDef struct {
Pos lexer.Position
Name *string `"define" @Ident @("-" Ident)* "{"`
InnerCmds []*Command `@@* "}"`
}
// Var stores any variables encountered while parsing a script
type Var struct {
Pos lexer.Position
Key string `"set" @Ident`
Index *Value `("[" @@ "]")?`
Value *Value `"to" @@`
}
// If stores any if statements encountered while parsing a script
type If struct {
Pos lexer.Position
Condition *Value `"if" @@ "{"`
InnerCmds []*Command `@@* "}"`
}
// RptLoop stores any repeat loops encountered while parsing a script
type RptLoop struct {
Pos lexer.Position
Times *int `"repeat" @Number "times" "{"`
IndexVar *string `(@Ident "in")?`
InnerCmds []*Command `@@* "}"`
}
// WhlLoop stores any while loops encountered while parsing a script
type WhlLoop struct {
Pos lexer.Position
Condition *Value `"loop" "while" @@ "{"`
InnerCmds []*Command `@@* "}"`
}

69
cmd/scpt/funcs.go Normal file
View File

@@ -0,0 +1,69 @@
package main
import (
"errors"
"fmt"
"github.com/gen2brain/dlgs"
"os"
"os/exec"
)
// Default function to display a dialog
func displayDialog(args map[string]interface{}) (interface{}, error) {
// Get title
title, ok := args["title"]
if !ok {
return nil, errors.New("title not provided")
}
// Get unnamed argument as text
text, ok := args[""]
if !ok {
return nil, errors.New("text not provided")
}
// Display correct dialog based on given type
switch args["type"] {
case "yesno":
// Display yes or no dialog, returning bool based on user input
return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true)
case "info":
// Display info dialog, returning bool based on success
return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text))
case "error":
// Display error dialog, returning bool based on success
return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text))
case "entry":
// Check if default text given
defaultText, ok := args["default"]
if !ok {
// Set to empty if not given
defaultText = ""
}
// Display entry dialog
input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText))
// Return user input
return input, err
default:
// If type unknown, return error
return nil, fmt.Errorf("unknown dialog type: %v", args["type"])
}
}
// Default function to run a shell script using `sh -c`
func doShellScript(args map[string]interface{}) (interface{}, error) {
// Get unnamed argument and assert its type as string
script, ok := args[""].(string)
// If assertion successful
if ok {
// Create new exec.Cmd containing `sh -c <script>`
cmd := exec.Command("sh", "-c", script)
// Set command I/O
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
// Run command
_ = cmd.Run()
return "", nil
} else {
return nil, errors.New("script not provided")
}
}

View File

@@ -1,30 +1,79 @@
package main
import (
"errors"
"flag"
"fmt"
"gitea.arsenm.dev/Arsen6331/scpt"
"io/ioutil"
"log"
"os"
)
func main() {
filePath := flag.String("file", "", "File to parse")
useStdin := flag.Bool("stdin", false, "Parse STDIN")
dumpAST := flag.Bool("dump-ast", false, "Dump the AST as JSON to STDOUT and quit")
loadAST := flag.String("load-ast", "", "Load JSON AST from specified file and execute it")
flag.Parse()
if *filePath == "" {
log.Fatalln("Use --file to specify a file to parse")
var ast *scpt.AST
if *loadAST != "" {
data, err := ioutil.ReadFile(*loadAST)
if err != nil {
log.Fatalln("Error opening specified file:", err)
}
ast, err = scpt.LoadAST(data)
if err != nil {
log.Fatalln("Error loading AST:", err)
}
} else if *useStdin {
var err error
ast, err = scpt.Parse(os.Stdin)
if err != nil {
log.Fatalln("Error parsing STDIN:", err)
}
} else {
if flag.NArg() < 1 {
log.Fatalln("Filepath or --stdin required")
}
filePath := flag.Args()[0]
file, err := os.Open(filePath)
if err != nil {
log.Fatalln("Error opening specified file:", err)
}
ast, err = scpt.Parse(file)
if err != nil {
log.Fatalln("Error parsing file:", err)
}
}
file, err := os.Open(*filePath)
if err != nil {
log.Fatalln("Error opening specified file:", err)
if *dumpAST {
data, err := ast.DumpPretty()
if err != nil {
log.Fatalln("Error dumping AST:", err)
}
fmt.Println(string(data))
os.Exit(0)
}
ast, err := scpt.Parse(file)
if err != nil {
log.Fatalln("Error parsing file:", err)
}
err = ast.Execute()
scpt.AddFuncs(scpt.FuncMap{
"print": scptPrint,
"display-dialog": displayDialog,
"do-shell-script": doShellScript,
})
err := ast.Execute()
if err != nil {
log.Fatalln("Error executing script:", err)
}
}
func scptPrint(args map[string]interface{}) (interface{}, error) {
val, ok := args[""]
if !ok {
return nil, errors.New("print requires an unnamed argument")
}
fmt.Println(val)
return nil, nil
}

View File

@@ -1,91 +1,119 @@
/*
Copyright (c) 2021 Arsen Musayelyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
package scpt
import (
"errors"
"fmt"
"github.com/gen2brain/dlgs"
"os"
"os/exec"
"strconv"
)
// Default function to display a dialog
func displayDialog(args map[string]interface{}) (interface{}, error) {
// Get title
title, ok := args["title"]
if !ok {
return nil, errors.New("title not provided")
}
// Get unnamed argument as text
text, ok := args[""]
if !ok {
return nil, errors.New("text not provided")
}
// Display correct dialog based on given type
switch args["type"] {
case "yesno":
// Display yes or no dialog, returning bool based on user input
return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true)
case "info":
// Display info dialog, returning bool based on success
return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text))
case "error":
// Display error dialog, returning bool based on success
return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text))
case "entry":
// Check if default text given
defaultText, ok := args["default"]
if !ok {
// Set to empty if not given
defaultText = ""
}
// Display entry dialog
input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText))
// Return user input
return input, err
default:
// If type unknown, return error
return nil, fmt.Errorf("unknown dialog type: %v", args["type"])
}
}
// Default function to run a shell script using `sh -c`
func doShellScript(args map[string]interface{}) (interface{}, error) {
// Get unnamed argument and assert its type as string
script, ok := args[""].(string)
// If assertion successful
if ok {
// Create new exec.Cmd containing `sh -c <script>`
cmd := exec.Command("sh", "-c", script)
// Set command I/O
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
// Run command
_ = cmd.Run()
return "", nil
} else {
return nil, errors.New("script not provided")
}
// Vars stores any variables set during script runtime
var Vars = map[string]interface{}{}
// FuncMap is a map of strings mapped to suitable script functions
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
// Funcs stores the functions allowed for use in a script
var Funcs = FuncMap{
"str": toString,
"num": parseNumber,
"bool": parseBool,
"break": setBreakLoop,
"append": appendArray,
"exit": scptExit,
"return": setReturn,
"print": scptPrint,
}
// Default function to convert unnamed argument to a string using fmt.Sprint
func toString(args map[string]interface{}) (interface{}, error) {
val, ok := args[""]
if !ok {
return nil, errors.New("no value provided")
}
return fmt.Sprint(val), nil
}
}
// Default function to parse unnamed argument to a number using strconv.ParseFloat
func parseNumber(args map[string]interface{}) (interface{}, error) {
val, ok := args[""].(string)
if !ok {
return nil, errors.New("no value provided")
}
return strconv.ParseFloat(val, 64)
}
// Default function to parse unnamed argument to a boolean using strconv.ParseBool
func parseBool(args map[string]interface{}) (interface{}, error) {
val, ok := args[""].(string)
if !ok {
return nil, errors.New("no value provided")
}
return strconv.ParseBool(val)
}
// Default function to set the breakLoop variable to true, breaking any loops that may be running
func setBreakLoop(_ map[string]interface{}) (interface{}, error) {
// If a loop is running
if loopRunning {
// Set breakLoop to true, breaking the loop on next cycle
breakLoop = true
} else {
return nil, errors.New("break not inside loop")
}
return nil, nil
}
// Default function to set the breakLoop variable to true, breaking any loops that may be running
func setReturn(args map[string]interface{}) (interface{}, error) {
// If a loop is running
if funcRunning {
// Set breakLoop to true, breaking the loop on next cycle
retValue = args[""]
} else {
return nil, errors.New("return not inside function")
}
return nil, nil
}
func scptExit(args map[string]interface{}) (interface{}, error) {
exitCode, ok := args[""].(float64)
if !ok {
return nil, errors.New("exit requires an unnamed number argument")
}
os.Exit(int(exitCode))
return nil, nil
}
// Default function that returns an array with an appended element
func appendArray(args map[string]interface{}) (interface{}, error) {
// Attempt to get unnamed argument and assert as []interface{}
val, ok := args[""].([]interface{})
if !ok {
return nil, errors.New("cannot append to non-array object")
}
// Attempt to get items argument and assert as []interface{}
items, ok := args["items"].([]interface{})
if !ok {
return nil, errors.New("items argument invalid or not provided")
}
// For every item in items argument
for _, item := range items {
// Append to unnamed argument
val = append(val, item)
}
// Return appended unnamed argument
return val, nil
}
// Print message via fmt.Println
func scptPrint(args map[string]interface{}) (interface{}, error) {
// Get message
val, ok := args[""]
if !ok {
return nil, errors.New("print requires an unnamed argument")
}
// Print message
fmt.Println(val)
return nil, nil
}

33
go.sum
View File

@@ -1,4 +1,3 @@
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/alecthomas/participle v0.7.1 h1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=
github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY=
@@ -6,66 +5,38 @@ github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAK
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/antonmedv/expr v1.8.9 h1:O9stiHmHHww9b4ozhPx7T6BK7fXfOCHJ8ybxf0833zw=
github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f h1:HPKrg4xeWLWOGAJGVJIYXWhVSdy2kaihuoSy7kBP7S4=
github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe h1:rcf1P0fm+1l0EjG16p06mYLj9gW9X36KgdHJ/88hS4g=
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498 h1:4CFNy7/q7P06AsIONZzuWy7jcdqEmYQvOZ9FAFZdbls=
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M=
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -7,11 +7,11 @@ import (
// Create custom stateful regex lexer
var scptLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
{"Ident", `[a-zA-Z]\w*`, nil},
{"Ident", `[a-zA-Z_]\w*`, nil},
{"String", `"[^"]*"`, nil},
{"Number", `(?:\d*\.)?\d+`, nil},
{"Punct", `[-[!@$&()_{}\|:;"',.?/]|]`, nil},
{"Punct", `[![@$&(){}\|:;"',.?]|]`, nil},
{"Whitespace", `[ \t\r\n]+`, nil},
{"Comment", `#[^\n]+`, nil},
{"Operator", `(>=|<=|>|<|==|!=)|[-+*/^%]`, nil},
{"Comment", `(###(.|\n)+###|#[^\n]+)`, nil},
{"Operator", `(>=|<=|>|<|==|!=)|[-+*%/^]`, nil},
}))

217
scpt.go
View File

@@ -1,17 +1,3 @@
/*
Copyright (c) 2021 Arsen Musayelyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
// Package scpt provides an interpreter for my simple applescript-like
// scripting language
package scpt
@@ -26,19 +12,6 @@ import (
"strings"
)
// Vars stores any variables set during script runtime
var Vars = map[string]interface{}{}
// FuncMap is a map of strings mapped to suitable script functions
type FuncMap map[string]func(map[string]interface{}) (interface{}, error)
// Funcs stores the functions allowed for use in a script
var Funcs = FuncMap{
"display-dialog": displayDialog,
"do-shell-script": doShellScript,
"string": toString,
}
// AddFuncs adds all functions from the provided FuncMap into
// the Funcs variable
func AddFuncs(fnMap FuncMap) {
@@ -95,62 +68,165 @@ func ParseValue(val *Value) (interface{}, error) {
// Return reference to subcommand
return val.SubCmd, nil
} else if val.VarVal != nil {
// Return value of provided key
return Vars[*val.VarVal], nil
} else if val.Expr != nil {
// Parse value of left side of expression
left, _ := callIfFunc(ParseValue(val.Expr.Left))
// If value is string, requote
if isStr(left) {
left = requoteStr(left.(string))
}
// Create new nil string
var right string
// For every right segment
for _, segment := range val.Expr.RightSegs {
// Parse value of right segment, calling it if it is a function
rVal, _ := callIfFunc(ParseValue(segment.Right))
// If value is string, requote
if isStr(rVal) {
rVal = requoteStr(rVal.(string))
// If variable access contains index
if val.VarVal.Index != nil {
// Get index value
index, err := callIfFunc(ParseValue(val.VarVal.Index))
if err != nil {
return nil, err
}
// Append right segment to right string
right = right + fmt.Sprintf(
" %s %v",
segment.Op,
rVal,
)
// Get requested variable and attempt to assert as []interface{}
slc, ok := Vars[*val.VarVal.Name].([]interface{})
// If assertion successful
if ok {
// Attempt to assert index as a 64-bit float
indexFlt, ok := index.(float64)
if !ok {
return nil, errors.New("array index must be a number")
}
// If requested index is out of range, return error
if int64(len(slc)) <= int64(indexFlt) {
return nil, fmt.Errorf("index %d is out of range with length %d", *val.VarVal.Index, len(slc))
}
// Return value at requested index of requested variable
return slc[int64(indexFlt)], nil
} else {
// If assertion unsuccessful, attempt to assert as a map[interface{}]interface{}
iMap, ok := Vars[*val.VarVal.Name].(map[interface{}]interface{})
if !ok {
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist or is not a map")
}
// Attempt to get value at requested key
val, ok := iMap[index]
if !ok {
return nil, fmt.Errorf("index %v does not exist in map", index)
}
// Return value at key with no error
return val, nil
}
} else {
// If index is absent, attempt to get variable value from Vars
value, ok := Vars[*val.VarVal.Name]
if !ok {
return nil, errors.New("variable " + *val.VarVal.Name + " does not exist")
}
// Return value with no error
return value, nil
}
// Create string expression from segments and operator
exp := fmt.Sprintf(
"%v %s",
left,
right,
)
// Compile string expression
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
} else if val.Expr != nil {
// If value is an expression, return evaluated expression
return evalExpr(*val.Expr)
} else if val.Array != nil {
// If value is an array, create new nil []interface{}
var iSlice []interface{}
// For each value in array
for _, value := range val.Array {
// Recursively parse value
iVal, err := callIfFunc(ParseValue(value))
if err != nil {
return nil, err
}
// Append value to []interface{}
iSlice = append(iSlice, iVal)
}
// Return []interface{]
return iSlice, nil
} else if val.Map != nil {
// If value is a map, create new empty map[interface{}]interface{}
iMap := map[interface{}]interface{}{}
// For each value in map
for _, value := range val.Map {
// Recursively parse value
iVal, err := ParseValue(value.Value)
if err != nil {
return nil, err
}
// Recursively parse key
iKey, err := ParseValue(value.Key)
if err != nil {
return nil, err
}
// Set key of map to value
iMap[iKey] = iVal
}
// Return map[interface{}]interface{}
return iMap, nil
} else if val.Opposite != nil {
value, err := callIfFunc(ParseValue(val.Opposite))
if err != nil {
return nil, err
}
// Run expression
out, err := expr.Run(program, Vars)
if err != nil {
return nil, err
boolean, ok := value.(bool)
if !ok {
return nil, errors.New("cannot take opposite of a non-boolean value")
}
// Return expression output value
return out, nil
return !boolean, nil
}
return nil, nil
}
// Evaluate given expression, returning its value and optionally an error
func evalExpr(expression Expression) (interface{}, error) {
// Parse value of left side of expression
left, _ := callIfFunc(ParseValue(expression.Left))
// If value is string, requote
if isStr(left) {
left = quoteStr(left.(string))
}
// Create new nil string
var right string
// For every right gsegment
for _, segment := range expression.RightSegs {
// Parse value of right segment, calling it if it is a function
rVal, _ := callIfFunc(ParseValue(segment.Right))
// If value is string, requote
if isStr(rVal) {
rVal = quoteStr(rVal)
}
// Append right segment to right string
right = right + fmt.Sprintf(
" %s %v",
segment.Op,
rVal,
)
}
// Create string expression from segments and operator
exp := fmt.Sprintf(
"%v %s",
left,
right,
)
// Compile string expression
program, err := expr.Compile(strings.ReplaceAll(exp, "^", "**"))
if err != nil {
return nil, err
}
// Run expression
out, err := expr.Run(program, Vars)
if err != nil {
return nil, err
}
// Return expression output value
return out, nil
}
// Add quotes to an unquoted string
func requoteStr(s string) string {
// Return quoted string
return `"` + s + `"`
func quoteStr(s interface{}) string {
// If s is nil
if s == nil {
// Return empty quotes
return `""`
} else {
// Otherwise return formatted string using %v (any value)
return fmt.Sprintf(`"%v"`, s)
}
}
// Check if i is a string
func isStr(i interface{}) bool {
if i == nil {
return true
}
// if type of input is string, return true
if reflect.TypeOf(i).String() == "string" {
return true
@@ -205,10 +281,7 @@ func UnwrapArgs(args []*Arg) (map[string]interface{}, error) {
// IsFuncCall checks if val is a FuncCall struct
func IsFuncCall(val interface{}) bool {
// If type of val is a pointer to FuncCall, return true
if reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{}) {
return true
}
return false
return reflect.TypeOf(val) == reflect.TypeOf(&FuncCall{})
}
// CallFunction executes a given function call in the form of

33
scpt.yaml Normal file
View File

@@ -0,0 +1,33 @@
# This file is a syntax file for https://github.com/zyedidia/highlight
# This file is also suitable for the micro text editor, to install, place in ~/.config/micro/syntax
filetype: scpt
detect:
filename: "\\.scpt$"
rules:
- special: "\\b(bool|str|num|append)\\b"
- statement: "\\b(define|set|to|loop|while|if|repeat|times|in|)\\b"
- statement: "\\$"
- symbol.operator: "(>=|<=|>|<|==|!=)|[-+*%/^]"
- symbol.brackets: "([(){}]|\\[|\\])"
- constant.number: "\\b(?:\\d*\\.)?\\d+\\b"
- constant.string:
start: "\""
end: "\""
rules: []
- constant.bool: "\\b(true|false)\\b"
- comment:
start: "###"
end: "###"
rules: []
- comment:
start: "#"
end: "$"
rules: []

63
test.scpt Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env scpt
set y to (display-dialog "Hello" with title 12 with type "yesno")
display-dialog "Goodbye" with title 21 with type "error"
do-shell-script "notify-send Test Notification"
@@ -14,8 +16,61 @@ if {3 == 3} {
}
}
do-shell-script "echo rpt"
repeat 5 times { z in
do-shell-script {"echo " + (string $z)}
}
print $z
}
print (str 10.5)
if (bool "true") {
print "True"
}
###
This is a multiline comment
###
# This is a single line comment
set hi to ["test", 3]
set hi[0] to "test2"
print $hi[0]
set hi to (append $hi with items [5, 4])
print {$hi[2] + $hi[3]}
set msi to [5: "hi", "hello": "world"]
set msi[5] to "hello"
print $msi[5]
print $msi["hello"]
set c to 0
set f to true
loop while $f {
set c to {$c + 1}
if {$c == 3} {
set f to false
}
print {"iter: " + (str $c)}
}
repeat 6 times { i in
print {"brktest: " + (str $i)}
if {$i == 3} {
break
}
print {"brktest: " + (str $i) + " (2)"}
}
define hello-x {
print {"Hello, " + $_args[""]}
return {"Hello, " + $_args[""]}
print "Something isn't right"
}
hello-x "Function"
set f to (hello-x "World")
print {"Returned: " + $f}