├── .gitignore ├── LICENSE ├── cli.go ├── core ├── builtins.go ├── defs.go ├── eval.go ├── list.go └── parser.go ├── debug.go ├── makefile ├── readme.md ├── stdlib ├── .gitignore ├── assert.golsp ├── os.golsp ├── os │ ├── makefile │ └── os.go ├── tools.golsp ├── types.golsp └── types │ ├── makefile │ └── types.go └── test-files ├── assert.golsp ├── boolean.golsp ├── const.golsp ├── do.golsp ├── dot.golsp ├── equals.golsp ├── fp.golsp ├── functions.golsp ├── go.golsp ├── go2.golsp ├── hello.golsp ├── lambda.golsp ├── lists.golsp ├── math.golsp ├── oop.golsp ├── os.golsp ├── os2.golsp ├── os3.golsp ├── os4.golsp ├── patterns.golsp ├── require ├── module │ ├── mod2 │ │ └── file.golsp │ └── module.golsp └── req.golsp ├── scope.golsp ├── spread.golsp ├── strings.golsp ├── syntax2.golsp ├── types.golsp ├── util.golsp ├── when.golsp ├── zip.golsp ├── zip2.golsp └── zip3.golsp /.gitignore: -------------------------------------------------------------------------------- 1 | /golsp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Ajay Tatachar 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cli.go: -------------------------------------------------------------------------------- 1 | 2 | // CLI 3 | 4 | package main 5 | 6 | import ( 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | golsp "github.com/ajaymt/golsp/core" 11 | // "fmt" 12 | ) 13 | 14 | func main() { 15 | filename := "-" 16 | dirname := "." 17 | file := os.Stdin 18 | var args []string 19 | 20 | if len(os.Args) > 1 { 21 | filename = os.Args[1] 22 | args = os.Args[2:] 23 | } 24 | 25 | if filename != "-" { 26 | filename, _ = filepath.Abs(filename) 27 | dirname = filepath.Dir(filename) 28 | file, _ = os.Open(filename) 29 | } 30 | 31 | input, _ := ioutil.ReadAll(file) 32 | // fmt.Println(PrintST(golsp.MakeST(golsp.Tokenize(string(input))))) 33 | golsp.Run(dirname, filename, args, string(input)) 34 | } 35 | -------------------------------------------------------------------------------- /core/builtins.go: -------------------------------------------------------------------------------- 1 | 2 | // Builtins 3 | 4 | package golsp 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | "strconv" 10 | "time" 11 | "path/filepath" 12 | "io/ioutil" 13 | "os" 14 | "plugin" 15 | ) 16 | 17 | var Builtins = Scope{} 18 | 19 | // InitializeBuiltins: Initialize the default builtin scope ('Builtins') 20 | // with builtin identifiers 21 | func InitializeBuiltins(dirname string, filename string, args []string) { 22 | identifiers := map[string]Object{ 23 | UNDEFINED: UndefinedObject(), 24 | DIRNAME: StringObject(dirname), 25 | FILENAME: StringObject(filename), 26 | ARGS: ListObject(args), 27 | 28 | "def": BuiltinFunctionObject("def", BuiltinDef), 29 | "const": BuiltinFunctionObject("const", BuiltinConst), 30 | "lambda": BuiltinFunctionObject("lambda", BuiltinLambda), 31 | "require": BuiltinFunctionObject("require", BuiltinRequire), 32 | "if": BuiltinFunctionObject("if", BuiltinIf), 33 | "when": BuiltinFunctionObject("when", BuiltinWhen), 34 | "do": BuiltinFunctionObject("do", BuiltinDo), 35 | "go": BuiltinFunctionObject("go", BuiltinGo), 36 | "sleep": BuiltinFunctionObject("sleep", BuiltinSleep), 37 | "sprintf": BuiltinFunctionObject("sprintf", BuiltinSprintf), 38 | "printf": BuiltinFunctionObject("printf", BuiltinPrintf), 39 | 40 | "+": BuiltinMathFunction("+"), 41 | "-": BuiltinMathFunction("-"), 42 | "*": BuiltinMathFunction("*"), 43 | "/": BuiltinMathFunction("/"), 44 | "%": BuiltinMathFunction("%"), 45 | 46 | "==": BuiltinComparisonFunction("=="), 47 | "!=": BuiltinComparisonFunction("!="), 48 | ">": BuiltinComparisonFunction(">"), 49 | "<": BuiltinComparisonFunction("<"), 50 | ">=": BuiltinComparisonFunction(">="), 51 | "<=": BuiltinComparisonFunction("<="), 52 | } 53 | 54 | Builtins.Identifiers = identifiers 55 | Builtins.Constants = make(map[string]bool) 56 | for k, _ := range identifiers { Builtins.Constants[k] = true } 57 | } 58 | 59 | // comparePatterns: Compare two function potterns (as passed to the '=' function) 60 | // to check whether they are identical. This function is used to check for and 61 | // redefine existing function patterns 62 | // `pattern1`: the first pattern 63 | // `pattern2`: the second pattern 64 | // this function returns whether the two patterns are identical 65 | func comparePatterns(pattern1 []STNode, pattern2 []STNode) bool { 66 | if len(pattern1) != len(pattern2) { return false } 67 | 68 | for i, node1 := range pattern1 { 69 | node2 := pattern2[i] 70 | 71 | if node1.Type != node2.Type { return false } 72 | if node1.Type == STNodeTypeStringLiteral || 73 | node1.Type == STNodeTypeNumberLiteral { 74 | if node1.Head != node2.Head { return false } 75 | } 76 | if node1.Spread != node2.Spread { return false } 77 | if node1.Type == STNodeTypeList { 78 | if !comparePatterns(node1.Children, node2.Children) { 79 | return false 80 | } 81 | } 82 | if node1.Type == STNodeTypeMap { 83 | if !comparePatterns(node1.Children, node2.Children) { 84 | return false 85 | } 86 | 87 | zip1 := make([]STNode, len(node1.Children)) 88 | zip2 := make([]STNode, len(node2.Children)) 89 | for j, z := range node1.Children { zip1[j] = *z.Zip } 90 | for j, z := range node2.Children { zip2[j] = *z.Zip } 91 | 92 | if !comparePatterns(zip1, zip2) { return false } 93 | } 94 | } 95 | 96 | return true 97 | } 98 | 99 | // isConstant: Check whether an identifier is constant within a given scope 100 | // and its parents 101 | // `scope`: the scope 102 | // `identifier`: the identifier 103 | // this function returns whether the identifer is a constant 104 | func isConstant(scope Scope, identifier string) bool { 105 | constant, exists := scope.Constants[identifier] 106 | if exists { return constant } 107 | if scope.Parent != nil { return isConstant(*scope.Parent, identifier) } 108 | 109 | return false 110 | } 111 | 112 | // see 'assign' 113 | func BuiltinDef(scope Scope, arguments []Object) Object { 114 | return assign(scope, arguments, false) 115 | } 116 | func BuiltinConst(scope Scope, arguments []Object) Object { 117 | return assign(scope, arguments, true) 118 | } 119 | 120 | // assign: The builtin 'def' and 'const' functions. These functions (re)bind identifiers 121 | // to objects and function patterns to expressions. They only act within their immediate 122 | // scope and do not cause side-effects elsewhere 123 | // `scope`: the scope within which the function is evaluated 124 | // `arguments`: the arguments passed to the function 125 | // `constant`: whether the identifier is being set as a constant 126 | // this function returns a result object -- for '=', this is the value that the 127 | // identifier or pattern was bound to 128 | func assign(scope Scope, arguments []Object, constant bool) Object { 129 | if len(arguments) < 2 { 130 | return UndefinedObject() 131 | } 132 | 133 | // as of now, '=' does not take spread expressions as arguments 134 | if arguments[0].Type != ObjectTypeBuiltinArgument || 135 | arguments[1].Type != ObjectTypeBuiltinArgument { 136 | return UndefinedObject() 137 | } 138 | 139 | symbol := arguments[0].Value 140 | value := arguments[1].Value 141 | 142 | // attempting to assign to a literal or list fails 143 | if symbol.Type != STNodeTypeIdentifier && 144 | symbol.Type != STNodeTypeExpression { 145 | return UndefinedObject() 146 | } 147 | 148 | if symbol.Type == STNodeTypeIdentifier { 149 | // attempting to assign to a constant identifier fails 150 | if isConstant(scope, symbol.Head) { 151 | return UndefinedObject() 152 | } 153 | 154 | // if the symbol is an identifier, the value is evaluated immediately 155 | // and symbol is bound to it 156 | obj := Eval(MakeScope(&scope), value) 157 | if obj.Type == ObjectTypeFunction { obj.Function.Name = symbol.Head } 158 | scope.Identifiers[symbol.Head] = obj 159 | if constant { scope.Constants[symbol.Head] = true } 160 | return scope.Identifiers[symbol.Head] 161 | } 162 | 163 | // at this point the symbol must be an expression, i.e '[functionName pattern...]' 164 | 165 | head := symbol.Children[0] 166 | if head.Type != STNodeTypeIdentifier { 167 | return UndefinedObject() 168 | } 169 | 170 | pattern := symbol.Children[1:] 171 | for i, _ := range pattern { 172 | patternscope := MakeScope(&scope) 173 | for pattern[i].Type == STNodeTypeExpression { 174 | obj := Eval(patternscope, pattern[i]) 175 | if obj.Type == ObjectTypeLiteral { 176 | pattern[i] = obj.Value 177 | } 178 | } 179 | } 180 | 181 | symbol = head 182 | if isConstant(scope, symbol.Head) { return UndefinedObject() } 183 | 184 | _, exists := scope.Identifiers[symbol.Head] 185 | if !exists { 186 | newscope := MakeScope(&scope) 187 | scope.Identifiers[symbol.Head] = Object{ 188 | Scope: newscope, 189 | Type: ObjectTypeFunction, 190 | } 191 | } 192 | 193 | patternexists := false 194 | patternindex := 0 195 | for index, p := range scope.Identifiers[symbol.Head].Function.FunctionPatterns { 196 | if comparePatterns(pattern, p) { 197 | patternexists = true 198 | patternindex = index 199 | break 200 | } 201 | } 202 | 203 | if patternexists { 204 | scope.Identifiers[symbol.Head].Function.FunctionBodies[patternindex] = value 205 | if constant { scope.Constants[symbol.Head] = true } 206 | return scope.Identifiers[symbol.Head] 207 | } 208 | 209 | newfn := Function{ 210 | Name: symbol.Head, 211 | FunctionPatterns: append(scope.Identifiers[symbol.Head].Function.FunctionPatterns, pattern), 212 | FunctionBodies: append(scope.Identifiers[symbol.Head].Function.FunctionBodies, value), 213 | } 214 | 215 | scope.Identifiers[symbol.Head] = Object{ 216 | Scope: MakeScope(&scope), 217 | Type: ObjectTypeFunction, 218 | Function: newfn, 219 | } 220 | 221 | if constant { scope.Constants[symbol.Head] = true } 222 | return scope.Identifiers[symbol.Head] 223 | } 224 | 225 | // BuiltinLambda: The builtin 'lambda' function. This produces a function-type 226 | // object with one pattern and one expression 227 | // this function returns the function object that is produced 228 | func BuiltinLambda(scope Scope, arguments []Object) Object { 229 | if len(arguments) < 2 { 230 | return UndefinedObject() 231 | } 232 | 233 | // as of now, 'lambda' does not take spread expressions as arguments 234 | if arguments[0].Type != ObjectTypeBuiltinArgument || 235 | arguments[1].Type != ObjectTypeBuiltinArgument { 236 | return UndefinedObject() 237 | } 238 | 239 | if arguments[0].Value.Type != STNodeTypeExpression { 240 | return UndefinedObject() 241 | } 242 | 243 | pattern := arguments[0].Value.Children 244 | body := arguments[1].Value 245 | 246 | for i, _ := range pattern { 247 | patternscope := MakeScope(&scope) 248 | for pattern[i].Type == STNodeTypeExpression { 249 | obj := Eval(patternscope, pattern[i]) 250 | if obj.Type == ObjectTypeLiteral { 251 | pattern[i] = obj.Value 252 | } 253 | } 254 | } 255 | 256 | fn := Function{ 257 | FunctionPatterns: [][]STNode{pattern}, 258 | FunctionBodies: []STNode{body}, 259 | } 260 | 261 | return Object{ 262 | Scope: MakeScope(&scope), 263 | Type: ObjectTypeFunction, 264 | Function: fn, 265 | } 266 | } 267 | 268 | // BuiltinRequire: The builtin 'require' function. This function evaluates a 269 | // file and returns the Object that the file exports. 270 | func BuiltinRequire(scope Scope, args []Object) Object { 271 | arguments := EvalArgs(scope, args) 272 | 273 | if len(arguments) < 1 { 274 | return UndefinedObject() 275 | } 276 | if arguments[0].Value.Type != STNodeTypeStringLiteral { 277 | return UndefinedObject() 278 | } 279 | 280 | dirnode := LookupIdentifier(scope, DIRNAME) 281 | dirname := dirnode.Value.Head[1:len(dirnode.Value.Head) - 1] 282 | rawpath := arguments[0].Value.Head[1:len(arguments[0].Value.Head) - 1] 283 | if strings.HasPrefix(rawpath, "stdlib/") { 284 | // TODO find a better way to do this 285 | dirname = os.Getenv("GOLSPPATH") 286 | } 287 | 288 | resolvedpath := filepath.Join(dirname, rawpath) 289 | 290 | if strings.HasSuffix(resolvedpath, ".so") { 291 | plug, err := plugin.Open(resolvedpath) 292 | if err != nil { return UndefinedObject() } 293 | exportssym, err := plug.Lookup("Exports") 294 | if err != nil { return UndefinedObject() } 295 | return *exportssym.(*Object) 296 | } 297 | 298 | file, err := os.Open(resolvedpath) 299 | if err != nil { return UndefinedObject() } 300 | data, err := ioutil.ReadAll(file) 301 | if err != nil { return UndefinedObject() } 302 | 303 | return Run(filepath.Dir(resolvedpath), resolvedpath, []string{}, string(data)) 304 | } 305 | 306 | // BuiltinMathFunction: Produce a builtin function for a given math operator 307 | // `op`: the math operator, one of + - * / % 308 | // this function returns a Object containing the builtin function for the math operator 309 | func BuiltinMathFunction(op string) Object { 310 | // fn: the produced math function that performs an operation specified by `op` 311 | // this function returns the result of the math operation 312 | fn := func (scope Scope, args []Object) Object { 313 | // math operations are undefined on non-numbers 314 | arguments := EvalArgs(scope, args) 315 | for _, a := range arguments { 316 | if a.Value.Type != STNodeTypeNumberLiteral { 317 | return UndefinedObject() 318 | } 319 | } 320 | 321 | result := 0.0 322 | numbers := make([]float64, len(arguments)) 323 | for i, arg := range arguments { 324 | numbers[i], _ = strconv.ParseFloat(arg.Value.Head, 64) 325 | } 326 | 327 | switch op { 328 | case "+": 329 | for _, n := range numbers { result += n } 330 | case "-": 331 | if len(numbers) > 0 { result += numbers[0] } 332 | for _, n := range numbers[1:] { result -= n } 333 | case "*": 334 | result = 1.0 335 | for _, n := range numbers { result *= n } 336 | case "/": 337 | numerator := 1.0 338 | if len(numbers) > 0 { numerator *= numbers[0] } 339 | denominator := 1.0 340 | for _, n := range numbers[1:] { denominator *= n } 341 | result = numerator / denominator 342 | case "%": 343 | numerator := 1.0 344 | if len(numbers) > 0 { numerator *= numbers[0] } 345 | denominator := 1.0 346 | for _, n := range numbers[1:] { denominator *= n } 347 | result = float64(int(numerator) % int(denominator)) 348 | } 349 | 350 | return NumberObject(result) 351 | } 352 | 353 | return BuiltinFunctionObject(op, fn) 354 | } 355 | 356 | // formatStr: Format a Go-style format string with a set of Object arguments 357 | // `text`: the format string 358 | // `objects`: the objects to serialize into the string 359 | // this function returns the formatted string 360 | func formatStr(text string, objects []Object) string { 361 | args := make([]interface{}, len(objects)) 362 | for i, v := range objects { 363 | if v.Type == ObjectTypeFunction { 364 | args[i] = fmt.Sprintf("", v.Function.Name) 365 | continue 366 | } 367 | 368 | if v.Type == ObjectTypeList { 369 | args[i] = fmt.Sprintf("{%v}", 370 | formatStr(strings.Repeat("%v ", v.Elements.Length), v.Elements.ToSlice())) 371 | continue 372 | } 373 | 374 | if v.Type == ObjectTypeMap { 375 | strs := make([]string, 0, len(v.MapKeys)) 376 | for _, key := range v.MapKeys { 377 | str := fmt.Sprintf("%v: %v", key.Value.Head, 378 | formatStr("%v", []Object{v.Map[key.Value.Head]})) 379 | strs = append(strs, str) 380 | } 381 | args[i] = fmt.Sprintf("map(%v)", strings.Join(strs, ", ")) 382 | continue 383 | } 384 | 385 | if v.Value.Type == STNodeTypeNumberLiteral { 386 | args[i], _ = ToNumber(v) 387 | } else if v.Value.Type == STNodeTypeStringLiteral { 388 | args[i], _ = ToString(v) 389 | } else { 390 | args[i] = "" 391 | } 392 | } 393 | 394 | return fmt.Sprintf(text, args...) 395 | } 396 | 397 | // BuiltinSprintf: The builtin 'sprintf' function. This function formats a 398 | // Go-style format string with a set of arguments 399 | // this function returns the formatted string 400 | func BuiltinSprintf(scope Scope, args []Object) Object { 401 | arguments := EvalArgs(scope, args) 402 | 403 | if arguments[0].Value.Type != STNodeTypeStringLiteral { 404 | return UndefinedObject() 405 | } 406 | 407 | text := arguments[0].Value.Head 408 | text = text[1:len(text) - 1] 409 | 410 | return StringObject(formatStr(text, arguments[1:])) 411 | } 412 | 413 | // BuiltinPrintf: The builtin 'printf' function. This function formats 414 | // a Go-style format string with a set of arguments and writes the result to 415 | // stdout 416 | // this function returns the formatted string 417 | func BuiltinPrintf(scope Scope, arguments []Object) Object { 418 | obj := BuiltinSprintf(scope, arguments) 419 | if obj.Value.Head != UNDEFINED { 420 | fmt.Printf(obj.Value.Head[1:len(obj.Value.Head) - 1]) 421 | } 422 | 423 | return obj 424 | } 425 | 426 | // BuiltinDo: The builtin 'do' function. This function evaluates a series of 427 | // statements within an enclosed, isolated scope 428 | // this function returns the result of evaluating the final statement 429 | // in the scope 430 | func BuiltinDo(scope Scope, arguments []Object) Object { 431 | // no support for spread arguments yet 432 | for _, a := range arguments { 433 | if a.Type != ObjectTypeBuiltinArgument { 434 | return UndefinedObject() 435 | } 436 | } 437 | 438 | args := make([]STNode, len(arguments)) 439 | for i, c := range arguments { args[i] = c.Value } 440 | 441 | scopenode := STNode{ 442 | Type: STNodeTypeScope, 443 | Children: args, 444 | } 445 | 446 | return Eval(scope, scopenode) 447 | } 448 | 449 | // BuiltinGo: The builtin 'go' function. This function concurrently evaluates 450 | // a series of statements within an enclosed, isolated scope 451 | // this function returns UNDEFINED 452 | func BuiltinGo(scope Scope, arguments []Object) Object { 453 | var result Object 454 | completed := false 455 | 456 | RuntimeWaitGroup().Add(1) 457 | go func () { 458 | defer RuntimeWaitGroup().Done() 459 | result = BuiltinDo(scope, arguments) 460 | completed = true 461 | }() 462 | 463 | wait := func(_ Scope, _ []Object) Object { 464 | for !completed {} 465 | return result 466 | } 467 | 468 | return MapObject(map[string]Object{ 469 | "wait": BuiltinFunctionObject("wait", wait), 470 | }) 471 | } 472 | 473 | // BuiltinSleep: the builtin 'sleep' function. This function waits for a 474 | // specified number of milliseconds 475 | // this function returns UNDEFINED 476 | func BuiltinSleep(scope Scope, arguments []Object) Object { 477 | argobjects := EvalArgs(scope, arguments) 478 | 479 | if argobjects[0].Type != ObjectTypeLiteral || 480 | argobjects[0].Value.Type != STNodeTypeNumberLiteral { 481 | return UndefinedObject() 482 | } 483 | 484 | duration, _ := strconv.ParseFloat(argobjects[0].Value.Head, 64) 485 | time.Sleep(time.Duration(duration) * time.Millisecond) 486 | 487 | return UndefinedObject() 488 | } 489 | 490 | // objectToBoolean: Convert a Object to a boolean. This function defines the 491 | // conditions of the builtin 'if' and 'when' functions 492 | // `obj`: the object 493 | // this function returns true or false depending on the type and contents of obj 494 | func objectToBoolean(obj Object) bool { 495 | if obj.Type == ObjectTypeLiteral { 496 | if obj.Value.Type == STNodeTypeNumberLiteral { 497 | return obj.Value.Head != "0" 498 | } 499 | if obj.Value.Type == STNodeTypeStringLiteral { 500 | return len(obj.Value.Head) > 2 501 | } 502 | } 503 | if obj.Type == ObjectTypeList { return obj.Elements.Length > 0 } 504 | if obj.Type == ObjectTypeMap { return len(obj.MapKeys) > 0 } 505 | if obj.Type == ObjectTypeFunction { return true } 506 | 507 | return false 508 | } 509 | 510 | // BuiltinIf: the builtin 'if' function. This function evaluates a predicate 511 | // and evaluates one of two expressions depending on the result of the predicate 512 | // the function returns the result of the expression that is evaluated, or 513 | // UNDEFINED 514 | func BuiltinIf(scope Scope, args []Object) Object { 515 | arguments := []Object{UndefinedObject()} 516 | 517 | if len(args) == 0 { return UndefinedObject() } 518 | 519 | if args[0].Type == ObjectTypeBuiltinArgument { 520 | argscope := MakeScope(&scope) 521 | if args[0].Value.Spread { 522 | spread := SpreadNode(argscope, args[0].Value) 523 | arguments = spread.ToSlice() 524 | } else { 525 | arguments[0] = Eval(argscope, args[0].Value) 526 | } 527 | } else { arguments[0] = args[0] } 528 | 529 | if objectToBoolean(arguments[0]) { 530 | if len(arguments) > 1 { return arguments[1] } 531 | if len(args) > 1 { return EvalArgs(scope, args[1:2])[0] } 532 | } 533 | 534 | if len(arguments) > 2 { return arguments[2] } 535 | if len(args) > 2 { return EvalArgs(scope, args[2:3])[0] } 536 | 537 | return UndefinedObject() 538 | } 539 | 540 | // BuiltinWhen: the builtin 'when' function. This function takes a set of predicate-body 541 | // pairs (expressed as zipped expressions), evaluates the predicates one by one and evaluates 542 | // a 'body' when it reaches a predicate that is true. 543 | // this function returns the result of the body expression that is evaluated, or UNDEFINED 544 | func BuiltinWhen(scope Scope, args []Object) Object { 545 | for _, arg := range args { 546 | if arg.Type != ObjectTypeBuiltinArgument { 547 | return UndefinedObject() 548 | } 549 | } 550 | 551 | scp := MakeScope(&scope) 552 | for _, arg := range args { 553 | obj := Eval(scp, arg.Value) 554 | if objectToBoolean(obj) { 555 | if arg.Value.Zip == nil { return UndefinedObject() } 556 | return Eval(scp, *arg.Value.Zip) 557 | } 558 | } 559 | 560 | return UndefinedObject() 561 | } 562 | 563 | // BuiltinComparisonFunction: This function produces a builtin comparison function 564 | // for the specified operator 565 | // `op`: the comparison operator, one of == != > < >= <= 566 | // this function retuns the produced builtin function 567 | func BuiltinComparisonFunction(op string) Object { 568 | // fn: the builtin comparison function. This function compares numbers and strings as of now 569 | // this function returns the result of the comparison operator 570 | fn := func (scope Scope, args []Object) Object { 571 | arguments := EvalArgs(scope, args) 572 | if len(arguments) != 2 { 573 | return UndefinedObject() 574 | } 575 | 576 | // TODO handle lists? 577 | 578 | if arguments[0].Type != ObjectTypeLiteral || 579 | arguments[1].Type != ObjectTypeLiteral { 580 | return NumberObject(0) 581 | } 582 | 583 | if arguments[0].Value.Head == UNDEFINED || 584 | arguments[1].Value.Head == UNDEFINED { 585 | result := arguments[0].Value.Head == UNDEFINED && 586 | arguments[1].Value.Head == UNDEFINED && 587 | strings.Contains(op, "=") 588 | 589 | resultint := 0 590 | if result { resultint = 1 } 591 | 592 | return NumberObject(float64(resultint)) 593 | } 594 | 595 | argtype := arguments[0].Value.Type 596 | if arguments[1].Value.Type != argtype { 597 | return UndefinedObject() 598 | } 599 | 600 | str1, str2 := "", "" 601 | num1, num2 := 0.0, 0.0 602 | 603 | if argtype == STNodeTypeStringLiteral { 604 | str1 = arguments[0].Value.Head[1:len(arguments[0].Value.Head) - 1] 605 | str2 = arguments[1].Value.Head[1:len(arguments[1].Value.Head) - 1] 606 | } else { 607 | num1, _ = strconv.ParseFloat(arguments[0].Value.Head, 64) 608 | num2, _ = strconv.ParseFloat(arguments[1].Value.Head, 64) 609 | } 610 | 611 | resultnum := 0.0 612 | result := false 613 | switch op { 614 | case "==": 615 | result = num1 == num2 616 | if argtype == STNodeTypeStringLiteral { result = str1 == str2 } 617 | case "!=": 618 | result = num1 != num2 619 | if argtype == STNodeTypeStringLiteral { result = str1 != str2 } 620 | case ">": 621 | result = num1 > num2 622 | if argtype == STNodeTypeStringLiteral { result = str1 > str2 } 623 | case "<": 624 | result = num1 < num2 625 | if argtype == STNodeTypeStringLiteral { result = str1 < str2 } 626 | case ">=": 627 | result = num1 >= num2 628 | if argtype == STNodeTypeStringLiteral { result = str1 >= str2 } 629 | case "<=": 630 | result = num1 <= num2 631 | if argtype == STNodeTypeStringLiteral { result = str1 <= str2 } 632 | } 633 | 634 | if result { resultnum = 1 } 635 | 636 | return NumberObject(resultnum) 637 | } 638 | 639 | return BuiltinFunctionObject(op, fn) 640 | } 641 | 642 | // EvalArgs: evaluate a list of arguments passed to builtin functions, 643 | // primarily used to handle spreading 644 | // `scp`: the scope within which to evaluate the arguments 645 | // `args`: the arguments to evaluate 646 | // this function returns the evaluated arguments as a list of Objects 647 | func EvalArgs(scp Scope, args []Object) []Object { 648 | scope := MakeScope(&scp) 649 | arglist := List{} 650 | for _, child := range args { 651 | if child.Type == ObjectTypeBuiltinArgument { 652 | node := child.Value 653 | if node.Spread { 654 | arglist.Join(SpreadNode(scope, node)) 655 | } else { 656 | arglist.Append(Eval(scope, node)) 657 | } 658 | } else { 659 | arglist.Append(child) 660 | } 661 | } 662 | 663 | return arglist.ToSlice() 664 | } 665 | -------------------------------------------------------------------------------- /core/defs.go: -------------------------------------------------------------------------------- 1 | 2 | // Definitions 3 | 4 | package golsp 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | "errors" 10 | ) 11 | 12 | // STNode: A single syntax tree node that has a 'head' (i.e value), type, 13 | // list of child nodes and flags/fields for operators 14 | 15 | type STNodeType int 16 | const ( 17 | STNodeTypeScope STNodeType = 0 18 | STNodeTypeExpression STNodeType = 1 19 | STNodeTypeStringLiteral STNodeType = 2 20 | STNodeTypeNumberLiteral STNodeType = 3 21 | STNodeTypeList STNodeType = 4 22 | STNodeTypeMap STNodeType = 5 23 | STNodeTypeIdentifier STNodeType = 6 24 | STNodeTypeComment STNodeType = 7 25 | ) 26 | 27 | type STNode struct { 28 | Head string 29 | Type STNodeType 30 | Children []STNode 31 | Spread bool 32 | Zip *STNode 33 | Dot *STNode 34 | } 35 | 36 | // /STNode 37 | 38 | // Scope: A 'scope' struct that has a parent scope and a map of strings 39 | // to objects 40 | 41 | type Scope struct { 42 | Parent *Scope 43 | Identifiers map[string]Object 44 | Constants map[string]bool 45 | } 46 | 47 | // /Scope 48 | 49 | // Function: A function struct that contains a name, list of patterns 50 | // for which it is defined and an expression (i.e function body) for each 51 | // pattern. If it is a builtin function (i.e implemented in Go), it contains a 52 | // function pointer with a specific signature 53 | 54 | type BuiltinFunction func(Scope, []Object) Object 55 | type Function struct { 56 | Name string 57 | FunctionPatterns [][]STNode 58 | FunctionBodies []STNode 59 | BuiltinFunc BuiltinFunction 60 | } 61 | 62 | // /Function 63 | 64 | // Object: A container for basic values that wraps literals, functions, 65 | // lists and maps. Contains a type, value (for literals), function struct 66 | // (for functions), list of elements (for lists) and map (for maps). The scope property 67 | // is the scope in which the object was created, primarily used to create closures 68 | 69 | type ObjectType int 70 | const ( 71 | ObjectTypeBuiltinArgument ObjectType = -1 72 | ObjectTypeLiteral ObjectType = 0 73 | ObjectTypeFunction ObjectType = 1 74 | ObjectTypeList ObjectType = 2 75 | ObjectTypeMap ObjectType = 3 76 | ) 77 | 78 | type Object struct { 79 | Scope Scope 80 | Type ObjectType 81 | Function Function 82 | Value STNode 83 | Elements List 84 | Map map[string]Object 85 | MapKeys []Object 86 | } 87 | 88 | // /Object 89 | 90 | // Object constructors 91 | 92 | // BuiltinFunctionObject: Produce a function object from a BuiltinFunction-type function 93 | // `name`: the name of the function 94 | // `fn`: the builtin function 95 | // this function returns the Object containing the builtin function 96 | func BuiltinFunctionObject(name string, fn BuiltinFunction) Object { 97 | return Object{ 98 | Type: ObjectTypeFunction, 99 | Function: Function{Name: name, BuiltinFunc: fn}, 100 | } 101 | } 102 | 103 | // UndefinedObject: Produce the UNDEFINED identifier object. 104 | // Used because structs and other data structures are mutable by default and cannot 105 | // be stored in consts 106 | // this function returns the UNDEFINED Object 107 | func UndefinedObject() Object { 108 | return Object{ 109 | Type: ObjectTypeLiteral, 110 | Value: STNode{ 111 | Head: UNDEFINED, 112 | Type: STNodeTypeIdentifier, 113 | }, 114 | } 115 | } 116 | 117 | // StringObject: Produce a string object from a string 118 | // `str`: the string 119 | // this function returns the produced Object 120 | func StringObject(str string) Object { 121 | return Object{ 122 | Type: ObjectTypeLiteral, 123 | Value: STNode{ 124 | Head: fmt.Sprintf("\"%s\"", str), 125 | Type: STNodeTypeStringLiteral, 126 | }, 127 | } 128 | } 129 | 130 | // NumberObject: Produce a number object from a number 131 | // `num`: the number 132 | // this function returns the produced Object 133 | func NumberObject(num float64) Object { 134 | var head string 135 | if float64(int(num)) == num { 136 | head = strconv.Itoa(int(num)) 137 | } else { head = fmt.Sprintf("%g", num) } 138 | 139 | return Object{ 140 | Type: ObjectTypeLiteral, 141 | Value: STNode{ 142 | Head: head, 143 | Type: STNodeTypeNumberLiteral, 144 | }, 145 | } 146 | } 147 | 148 | // MapObject: Produce a map object from a map of strings 149 | // to Objects. This function cannot produce maps that bind numbers 150 | // to objects 151 | // `gomap`: the map 152 | // this function returns the produced Object 153 | func MapObject(gomap map[string]Object) Object { 154 | object := Object{ 155 | Type: ObjectTypeMap, 156 | Map: make(map[string]Object), 157 | MapKeys: make([]Object, 0, len(gomap)), 158 | } 159 | for k, v := range gomap { 160 | strobj := StringObject(k) 161 | object.Map[strobj.Value.Head] = v 162 | object.MapKeys = append(object.MapKeys, strobj) 163 | } 164 | 165 | return object 166 | } 167 | 168 | // ListObject: Produce a list object from a slice of strings. 169 | // This function cannot produce lists that contain non-string objects 170 | // `slice`: the slice 171 | // this function returns the produced Object 172 | func ListObject(slice []string) Object { 173 | object := Object{ 174 | Type: ObjectTypeList, 175 | Elements: List{}, 176 | } 177 | for _, str := range slice { 178 | object.Elements.Append(StringObject(str)) 179 | } 180 | 181 | return object 182 | } 183 | 184 | // /Object constructors 185 | 186 | // Object de-constructors 187 | 188 | // ToString: Extract a string from a string object. This function cannot 189 | // convert non-string objects to strings 190 | // `obj`: the string object 191 | // this function returns a string and an optional error 192 | func ToString(obj Object) (string, error) { 193 | if obj.Value.Type != STNodeTypeStringLiteral { 194 | return "", errors.New("Cannot convert non-string object to string") 195 | } 196 | 197 | return obj.Value.Head[1:len(obj.Value.Head) - 1], nil 198 | } 199 | 200 | // ToNumber: Extract a number from a number object. This function cannot 201 | // convert non-number objects to numbers 202 | // `obj`: the number object 203 | // this function returns a number (as a float64) and an optional error 204 | func ToNumber(obj Object) (float64, error) { 205 | if obj.Value.Type != STNodeTypeNumberLiteral { 206 | return -1, errors.New("Cannot convert non-number object to number") 207 | } 208 | 209 | return strconv.ParseFloat(obj.Value.Head, 64) 210 | } 211 | 212 | // /Object de-constructors 213 | 214 | // names of special builtin identifiers 215 | const UNDEFINED = "undefined" 216 | const DIRNAME = "__dirname__" 217 | const FILENAME = "__filename__" 218 | const ARGS = "__args__" 219 | -------------------------------------------------------------------------------- /core/eval.go: -------------------------------------------------------------------------------- 1 | 2 | // Evaluator 3 | 4 | package golsp 5 | 6 | import ( 7 | "fmt" 8 | "sync" 9 | ) 10 | 11 | // runtimeWaitGroup is the wait group used to wait for 'go' blocks to complete 12 | var runtimeWaitGroup sync.WaitGroup 13 | func RuntimeWaitGroup() *sync.WaitGroup { 14 | return &runtimeWaitGroup 15 | } 16 | 17 | // comparePatternNode: Compare a node in a function pattern with an argument object 18 | // `pattern`: the pattern node 19 | // `arg`: the argument to compare with the pattern 20 | // this function returns whether the argument matches the pattern node 21 | func comparePatternNode(pattern STNode, arg Object) bool { 22 | // identifiers i.e non-literal patterns match everything 23 | if pattern.Type == STNodeTypeIdentifier { return true } 24 | 25 | // literal patterns match arguments that have the same value 26 | if pattern.Type == STNodeTypeStringLiteral || 27 | pattern.Type == STNodeTypeNumberLiteral { 28 | return arg.Value.Head == pattern.Head 29 | } 30 | 31 | // map patterns match if all the specified keys and values match 32 | // value-only matching i.e `[foo ( quux: "hello" )]` does not work yet 33 | if pattern.Type == STNodeTypeMap { 34 | if arg.Type != ObjectTypeMap { return false } 35 | 36 | for i, c := range pattern.Children { 37 | if c.Spread && c.Type == STNodeTypeIdentifier { 38 | return len(arg.MapKeys) >= i 39 | } 40 | if len(arg.MapKeys) <= i { return false } 41 | if c.Type == STNodeTypeStringLiteral || c.Type == STNodeTypeNumberLiteral { 42 | value, exists := arg.Map[c.Head] 43 | if !exists { return false } 44 | if c.Zip != nil { 45 | if !comparePatternNode(*c.Zip, value) { return false } 46 | } 47 | } 48 | } 49 | 50 | if len(arg.MapKeys) > len(pattern.Children) { return false } 51 | } 52 | 53 | // list patterns match if each of their elements match and the lists 54 | // are of the same length, after accounting for spreading 55 | if pattern.Type == STNodeTypeList { 56 | if arg.Type != ObjectTypeList { return false } 57 | 58 | ce := arg.Elements.First 59 | for i := 0; i < len(pattern.Children); ce, i = arg.Elements.Next(ce, i), i + 1 { 60 | child := pattern.Children[i] 61 | if child.Spread && child.Type == STNodeTypeIdentifier { 62 | return arg.Elements.Length >= i 63 | } 64 | if arg.Elements.Length <= i { return false } 65 | if !comparePatternNode(child, ce.Object) { return false } 66 | } 67 | 68 | if arg.Elements.Length > len(pattern.Children) { return false } 69 | } 70 | 71 | return true 72 | } 73 | 74 | // matchPatterns: Match a list of arguments to a particular function pattern 75 | // `fn`: the function whose patterns to check 76 | // `arguments`: the list of arguments to match to a pattern 77 | // this function returns the index of the best-matching pattern in function's 78 | // list of patterns, and whether a matching pattern was found 79 | func matchPatterns(fn Function, arguments List) (int, bool) { 80 | patterns := fn.FunctionPatterns 81 | bestscore := 0 82 | bestdiff := -1 83 | matchindex := 0 84 | found := false 85 | 86 | for i, p := range patterns { 87 | score := 0 88 | diff := 0 89 | minlen := len(p) 90 | if len(p) > arguments.Length { 91 | diff = len(p) - arguments.Length 92 | minlen = arguments.Length 93 | } 94 | 95 | if len(p) == 0 { found = true } 96 | 97 | ca := arguments.First 98 | for j := 0; j < minlen; ca, j = arguments.Next(ca, j), j + 1 { 99 | if comparePatternNode(p[j], ca.Object) { 100 | found = true 101 | score++ 102 | } 103 | if p[j].Spread { 104 | score += arguments.Length - 1 - j 105 | break 106 | } 107 | } 108 | 109 | if bestdiff == -1 { bestdiff = diff } 110 | if score > bestscore || (score == bestscore && diff < bestdiff) { 111 | matchindex = i 112 | } 113 | if diff < bestdiff { bestdiff = diff } 114 | if score > bestscore { bestscore = score } 115 | } 116 | 117 | return matchindex, found 118 | } 119 | 120 | // LookupIdentifier: lookup an identifier within a particular scope 121 | // `scope`: the scope in which to search for the identifier 122 | // `identifier`: the name of the identifier 123 | // this function returns the object corresponding to the identifier 124 | // or UNDEFINED 125 | func LookupIdentifier(scope Scope, identifier string) Object { 126 | obj, exists := scope.Identifiers[identifier] 127 | if exists { return obj } 128 | 129 | if scope.Parent != nil { 130 | return LookupIdentifier(*(scope.Parent), identifier) 131 | } 132 | 133 | return UndefinedObject() 134 | } 135 | 136 | // MakeScope: construct a new child scope that descends from a parent scope 137 | // `parent`: the parent scope 138 | // this function returns a new Scope struct whose Parent points to 139 | // parent 140 | func MakeScope(parent *Scope) Scope { 141 | newscope := Scope{ 142 | Parent: parent, 143 | Identifiers: make(map[string]Object), 144 | Constants: make(map[string]bool, len(parent.Constants)), 145 | } 146 | for k, v := range parent.Constants { newscope.Constants[k] = v } 147 | 148 | return newscope 149 | } 150 | 151 | // CopyFunction: Copy a Function struct 152 | // `fn`: the function to copy 153 | // this function returns a copy of fn 154 | func CopyFunction(fn Function) Function { 155 | fncopy := Function{ 156 | Name: fn.Name, 157 | FunctionPatterns: make([][]STNode, len(fn.FunctionPatterns)), 158 | FunctionBodies: make([]STNode, len(fn.FunctionBodies)), 159 | BuiltinFunc: fn.BuiltinFunc, 160 | } 161 | copy(fncopy.FunctionPatterns, fn.FunctionPatterns) 162 | copy(fncopy.FunctionBodies, fn.FunctionBodies) 163 | 164 | return fncopy 165 | } 166 | 167 | // CopyObject: Copy an Object 168 | // `object`: the object to copy 169 | // this function returns a copy of object. Note that it does not copy 170 | // object.Value since that property is never modified 171 | func CopyObject(object Object) Object { 172 | newobject := Object{ 173 | Type: object.Type, 174 | Value: object.Value, 175 | Function: CopyFunction(object.Function), 176 | Elements: object.Elements.Copy(), 177 | MapKeys: make([]Object, len(object.MapKeys)), 178 | Map: make(map[string]Object, len(object.Map)), 179 | Scope: Scope{ 180 | Parent: object.Scope.Parent, 181 | Identifiers: make(map[string]Object, len(object.Scope.Identifiers)), 182 | Constants: make(map[string]bool, len(object.Scope.Constants)), 183 | }, 184 | } 185 | 186 | for k, o := range object.Scope.Identifiers { newobject.Scope.Identifiers[k] = CopyObject(o) } 187 | for k, v := range object.Scope.Constants { newobject.Scope.Constants[k] = v } 188 | for i, k := range object.MapKeys { newobject.MapKeys[i] = CopyObject(k) } 189 | for k, v := range object.Map { newobject.Map[k] = CopyObject(v) } 190 | 191 | return newobject 192 | } 193 | 194 | // IsolateScope: 'Isolate' a scope object by copying all values from its parent 195 | // scopes into the scope struct, effectively orphaning it and flattening its 196 | // inheritance tree 197 | // `scope`: the scope to isolate 198 | // this function returns the isolated scope 199 | func IsolateScope(scope Scope) Scope { 200 | newscope := Scope{ 201 | Identifiers: make(map[string]Object, len(scope.Identifiers)), 202 | Constants: make(map[string]bool, len(scope.Constants)), 203 | } 204 | if scope.Parent != nil { 205 | parent := IsolateScope(*(scope.Parent)) 206 | for k, obj := range parent.Identifiers { 207 | obj.Scope.Parent = &newscope 208 | newscope.Identifiers[k] = obj 209 | } 210 | for k, v := range parent.Constants { newscope.Constants[k] = v } 211 | } 212 | for k, o := range scope.Identifiers { 213 | obj := CopyObject(o) 214 | obj.Scope.Parent = &newscope 215 | newscope.Identifiers[k] = obj 216 | } 217 | for k, v := range scope.Constants { newscope.Constants[k] = v } 218 | 219 | return newscope 220 | } 221 | 222 | // EvalSlice: Evaluate a slice expression, i.e `[list begin end step]` 223 | // `list`: the list or string that is sliced 224 | // `arguments`: the arguments passed in the expression 225 | // this function returns a slice of the list/string or UNDEFINED 226 | func EvalSlice(list Object, arguments List) Object { 227 | if arguments.Length == 0 { return list } 228 | 229 | for c, i := arguments.First, 0; i < arguments.Length; c, i = arguments.Next(c, i), i + 1 { 230 | if c.Object.Value.Type != STNodeTypeNumberLiteral && c.Object.Value.Head != UNDEFINED { 231 | return UndefinedObject() 232 | } 233 | } 234 | 235 | if list.Type == ObjectTypeList { 236 | beginf, err := ToNumber(arguments.First.Object) 237 | if err != nil { return UndefinedObject() } 238 | begin := int(beginf) 239 | switch arguments.Length { 240 | case 1: 241 | return list.Elements.Index(begin) 242 | case 2: 243 | endf, err := ToNumber(arguments.Next(arguments.First, 0).Object) 244 | end := int(endf) 245 | if err != nil { end = list.Elements.Length } 246 | return list.Elements.Slice(begin, end) 247 | default: 248 | secondarg := arguments.Next(arguments.First, 0) 249 | endf, parseEndErr := ToNumber(secondarg.Object) 250 | end := int(endf) 251 | sliceAll := false 252 | stepf, err := ToNumber(arguments.Next(secondarg, 1).Object) 253 | step := int(stepf) 254 | if err != nil { return UndefinedObject() } 255 | if parseEndErr != nil { 256 | sliceAll = true 257 | if step > 0 { end = list.Elements.Length } 258 | if step < 0 { end = -1 } 259 | } 260 | return list.Elements.SliceStep(begin, end, step, sliceAll) 261 | } 262 | } 263 | 264 | strlen := len(list.Value.Head) - 2 265 | byteslice := list.Value.Head[1:strlen + 1] 266 | beginf, err := ToNumber(arguments.First.Object) 267 | if err != nil { return UndefinedObject() } 268 | begin := int(beginf) 269 | if begin < 0 { begin += strlen } 270 | if begin < 0 || begin >= strlen { return UndefinedObject() } 271 | 272 | if arguments.Length == 1 { 273 | return StringObject(string(byteslice[begin:begin + 1])) 274 | } 275 | 276 | step := 1 277 | endf, parseEndErr := ToNumber(arguments.Next(arguments.First, 0).Object) 278 | end := int(endf) 279 | if end < 0 { end += strlen } 280 | if end < 0 { return UndefinedObject() } 281 | if end > strlen { end = strlen } 282 | 283 | if arguments.Length > 2 { 284 | secondarg := arguments.Next(arguments.First, 0) 285 | stepf, err := ToNumber(arguments.Next(secondarg, 1).Object) 286 | step = int(stepf) 287 | if step == 0 || err != nil { return UndefinedObject() } 288 | } 289 | 290 | if parseEndErr != nil { 291 | if step > 0 { end = strlen } 292 | if step < 0 { end = -1 } 293 | } 294 | 295 | if step == 1 { 296 | return StringObject(byteslice[begin:end]) 297 | } 298 | 299 | newbyteslice := make([]byte, 0, strlen) 300 | for i := begin; i != end; i += step { 301 | if i >= strlen { break } 302 | if i < 0 { break } 303 | 304 | newbyteslice = append(newbyteslice, byteslice[i]) 305 | } 306 | 307 | return StringObject(string(newbyteslice)) 308 | } 309 | 310 | // EvalMap: Lookup key(s) in a map 311 | // `glmap`: the map object 312 | // `arguments`: the key or keys to look up 313 | // this function returns the object or list of objects that the key(s) map to 314 | func EvalMap(glmap Object, arguments List) Object { 315 | if arguments.Length == 0 { return glmap } 316 | if arguments.Length == 1 { 317 | firstarg := arguments.First.Object 318 | value, exists := glmap.Map[firstarg.Value.Head] 319 | if firstarg.Type != ObjectTypeLiteral || !exists { 320 | UndefinedObject() 321 | } 322 | return value 323 | } 324 | 325 | values := List{} 326 | for c, i := arguments.First, 0; i < arguments.Length; c, i = arguments.Next(c, i), i + 1 { 327 | value, exists := glmap.Map[c.Object.Value.Head] 328 | if c.Object.Type != ObjectTypeLiteral || !exists { 329 | values.Append(UndefinedObject()) 330 | } else { 331 | values.Append(value) 332 | } 333 | } 334 | 335 | return Object{ 336 | Type: ObjectTypeList, 337 | Elements: values, 338 | } 339 | } 340 | 341 | // SpreadNode: Apply the spread operator to a syntax tree node 342 | // `scope`: the scope within which the node is being spread 343 | // `node`: the node to spread 344 | // this function returns the List of Objects that the node spreads to 345 | func SpreadNode(scope Scope, node STNode) List { 346 | nodescope := MakeScope(&scope) 347 | obj := Eval(nodescope, node) 348 | list := List{} 349 | if obj.Value.Head == UNDEFINED { return list } 350 | 351 | if obj.Type == ObjectTypeFunction || obj.Value.Type == STNodeTypeNumberLiteral { 352 | list.Append(obj) 353 | return list 354 | } 355 | 356 | if obj.Type == ObjectTypeList { return obj.Elements } 357 | if obj.Type == ObjectTypeMap { 358 | return ListFromSlice(obj.MapKeys) 359 | } 360 | 361 | str := obj.Value.Head[1:len(obj.Value.Head) - 1] 362 | for _, r := range str { 363 | list.Append(StringObject(string(r))) 364 | } 365 | 366 | return list 367 | } 368 | 369 | // bindArguments: Bind the arguments passed to a function to the function 370 | // object's Scope property 371 | // `exprhead`: the 'expression head' i.e function object 372 | // `pattern`: the matched pattern, based on which arguments will be bound 373 | // to identifiers 374 | // `argobjects`: the arguments passed to the function that will be bound to 375 | // identifiers 376 | func bindArguments(exprhead Object, pattern []STNode, argobjects List) { 377 | currentarg := argobjects.First 378 | for i := 0; i < len(pattern); currentarg, i = argobjects.Next(currentarg, i), i + 1 { 379 | symbol := pattern[i] 380 | if symbol.Type == STNodeTypeStringLiteral || symbol.Type == STNodeTypeNumberLiteral { 381 | continue 382 | } 383 | 384 | if symbol.Type == STNodeTypeIdentifier { 385 | if symbol.Spread { 386 | exprhead.Scope.Identifiers[symbol.Head] = Object{ 387 | Type: ObjectTypeList, 388 | Elements: argobjects.sublist(currentarg, i), 389 | } 390 | break 391 | } 392 | exprhead.Scope.Identifiers[symbol.Head] = currentarg.Object 393 | continue 394 | } 395 | 396 | if currentarg.Object.Type == ObjectTypeList && symbol.Type == STNodeTypeList { 397 | bindArguments(exprhead, symbol.Children, currentarg.Object.Elements) 398 | } 399 | 400 | if currentarg.Object.Type == ObjectTypeMap && symbol.Type == STNodeTypeMap { 401 | // this is a giant mess. clean it up 402 | mapped := make(map[string]bool) 403 | for _, child := range symbol.Children { 404 | if !(child.Type == STNodeTypeNumberLiteral || 405 | child.Type == STNodeTypeStringLiteral) { 406 | break 407 | } 408 | 409 | if child.Zip == nil { continue } 410 | 411 | value, exists := currentarg.Object.Map[child.Head] 412 | if !exists { continue } 413 | 414 | bindArguments(exprhead, []STNode{*child.Zip}, ListFromSlice([]Object{value})) 415 | mapped[child.Head] = true 416 | } 417 | 418 | keys := List{} 419 | values := List{} 420 | for _, key := range currentarg.Object.MapKeys { 421 | if !mapped[key.Value.Head] { 422 | keys.Append(key) 423 | values.Append(currentarg.Object.Map[key.Value.Head]) 424 | } 425 | } 426 | 427 | patternkeys := make([]STNode, len(symbol.Children) - len(mapped)) 428 | patternvalues := make([]STNode, 0, len(patternkeys)) 429 | for i := 0; i < len(patternkeys); i++ { 430 | c := symbol.Children[i + len(mapped)] 431 | patternkeys[i] = c 432 | if c.Zip == nil { continue } 433 | patternvalues = append(patternvalues, *c.Zip) 434 | } 435 | 436 | bindArguments(exprhead, patternkeys, keys) 437 | bindArguments(exprhead, patternvalues, values) 438 | } 439 | } 440 | } 441 | 442 | // evalDot: Evaluate 'dot' property access operator on map objects 443 | // `obj`: the (map) object 444 | // `root`: the syntax tree node associated with the dot operator 445 | // this function returns the value from the map object 446 | func evalDot(obj Object, root STNode) Object { 447 | if root.Dot == nil { return obj } 448 | if obj.Type != ObjectTypeMap { return UndefinedObject() } 449 | if root.Dot.Type != STNodeTypeIdentifier { return UndefinedObject() } 450 | 451 | key := fmt.Sprintf("\"%s\"", root.Dot.Head) 452 | value, exists := obj.Map[key] 453 | if !exists { return UndefinedObject() } 454 | 455 | return evalDot(value, *root.Dot) 456 | } 457 | 458 | // CallFunction: call a function object with a list of arguments 459 | // `fnobj`: the function object 460 | // `args`: the list of arguments 461 | // this function returns the result of the function call 462 | func CallFunction(fnobj Object, args List) Object { 463 | if fnobj.Type != ObjectTypeFunction { return UndefinedObject() } 464 | 465 | fnobj.Scope.Identifiers = make(map[string]Object, len(fnobj.Scope.Identifiers)) 466 | patternindex, patternfound := matchPatterns(fnobj.Function, args) 467 | if !patternfound { return UndefinedObject() } 468 | 469 | pattern := fnobj.Function.FunctionPatterns[patternindex] 470 | body := fnobj.Function.FunctionBodies[patternindex] 471 | if args.Length < len(pattern) { return UndefinedObject() } 472 | 473 | bindArguments(fnobj, pattern, args) 474 | 475 | return Eval(fnobj.Scope, body) 476 | } 477 | 478 | // Eval: Evaluate a syntax tree node within a scope 479 | // `scope`: the scope within which to evaluate the node 480 | // `root`: the root node to evaluate 481 | // this function returns the result of evaluating the node as an Object 482 | func Eval(scope Scope, root STNode) Object { 483 | // root node is a scope -- it evaluates to the result of the last expression 484 | // in the scope 485 | // scope nodes are isolated from their parents to ensure that they do not 486 | // cause side-effects, especially important for 'go' blocks 487 | if root.Type == STNodeTypeScope { 488 | newscope := IsolateScope(scope) 489 | var result Object 490 | for _, child := range root.Children { 491 | if child.Spread { 492 | spread := SpreadNode(newscope, child) 493 | result = spread.Last.Object 494 | } else { 495 | result = Eval(newscope, child) 496 | } 497 | } 498 | 499 | return evalDot(CopyObject(result), root) 500 | } 501 | 502 | // string and number literals simply evaluate to themselves 503 | if root.Type == STNodeTypeNumberLiteral || root.Type == STNodeTypeStringLiteral { 504 | result := Object{ 505 | Type: ObjectTypeLiteral, 506 | Value: root, 507 | } 508 | return evalDot(result, root) 509 | } 510 | 511 | // identifers evaluate to their corresponding values within the scope or UNDEFINED 512 | if root.Type == STNodeTypeIdentifier { 513 | return evalDot(LookupIdentifier(scope, root.Head), root) 514 | } 515 | 516 | // 'list' type syntax tree nodes evaluate to 'list' type Objects 517 | // note that list elements are evaluated immediately, unlike quote expressions 518 | // in Lisp 519 | if root.Type == STNodeTypeList { 520 | elements := List{} 521 | for _, c := range root.Children { 522 | if c.Spread { 523 | elements.Join(SpreadNode(scope, c)) 524 | } else { 525 | elements.Append(Eval(MakeScope(&scope), c)) 526 | } 527 | } 528 | 529 | result := Object{ 530 | Type: ObjectTypeList, 531 | Elements: elements, 532 | } 533 | return evalDot(result, root) 534 | } 535 | 536 | // 'map' type syntax tree nodes evaluate to maps 537 | if root.Type == STNodeTypeMap { 538 | obj := Object{ 539 | Type: ObjectTypeMap, 540 | Map: make(map[string]Object, len(root.Children)), 541 | MapKeys: make([]Object, 0, len(root.Children)), 542 | } 543 | 544 | for _, c := range root.Children { 545 | if c.Zip == nil { continue } 546 | left := List{} 547 | right := List{} 548 | 549 | if c.Spread { 550 | left = SpreadNode(scope, c) 551 | } else { 552 | left.Append(Eval(MakeScope(&scope), c)) 553 | } 554 | if c.Zip.Spread { 555 | right = SpreadNode(scope, *c.Zip) 556 | } else { 557 | right.Append(Eval(MakeScope(&scope), *c.Zip)) 558 | } 559 | 560 | minlen := left.Length; if right.Length < left.Length { minlen = right.Length } 561 | key := left.First 562 | value := right.First 563 | for index := 0; index < minlen; index++ { 564 | if key.Object.Type != ObjectTypeLiteral { continue } 565 | 566 | _, exists := obj.Map[key.Object.Value.Head] 567 | obj.Map[key.Object.Value.Head] = value.Object 568 | if !exists { 569 | obj.MapKeys = append(obj.MapKeys, key.Object) 570 | } 571 | 572 | key = left.Next(key, index) 573 | value = right.Next(value, index) 574 | } 575 | } 576 | 577 | return evalDot(obj, root) 578 | } 579 | 580 | // at this point the root node must be an expression 581 | 582 | // empty expressions evaluate to UNDEFINED 583 | if len(root.Children) == 0 { 584 | return evalDot(UndefinedObject(), root) 585 | } 586 | 587 | // exprhead is the head of the expression, aka the function 588 | // that is being called, list that is being sliced, etc... 589 | // argobjects is the rest of the expression, the arguments passed 590 | // to exprhead 591 | // arguments are evaluated in their own scope (argscope) to prevent side effects 592 | var exprhead Object 593 | argobjects := List{} 594 | argscope := MakeScope(&scope) 595 | 596 | if root.Children[0].Spread { 597 | spread := SpreadNode(scope, root.Children[0]) 598 | if spread.Length == 0 { return UndefinedObject() } 599 | exprhead = spread.First.Object 600 | argobjects = spread.slice(1, spread.Length) 601 | } else { 602 | exprhead = Eval(MakeScope(&scope), root.Children[0]) 603 | } 604 | 605 | // the function's argument scope is cleared every time it is called 606 | // since the arguments will be bound again 607 | if exprhead.Type == ObjectTypeFunction { 608 | exprhead.Scope.Identifiers = make(map[string]Object, len(exprhead.Scope.Identifiers)) 609 | } 610 | 611 | // evaluating an expression with a number literal or UNDEFINED head 612 | // produces the literal or UNDEFINED 613 | // i.e [1 2 3] evals to 1, [undefined a b c] evals to undefined 614 | if exprhead.Type == ObjectTypeLiteral && 615 | (exprhead.Value.Type == STNodeTypeNumberLiteral || 616 | exprhead.Value.Head == UNDEFINED) { 617 | return evalDot(exprhead, root) 618 | } 619 | 620 | // if exprhead is a list or string literal, slice it 621 | // if it is a map, lookup key 622 | if exprhead.Type == ObjectTypeList || 623 | exprhead.Type == ObjectTypeMap || 624 | exprhead.Value.Type == STNodeTypeStringLiteral { 625 | for _, c := range root.Children[1:] { 626 | if c.Spread { 627 | argobjects.Join(SpreadNode(argscope, c)) 628 | } else { 629 | argobjects.Append(Eval(argscope, c)) 630 | } 631 | } 632 | 633 | if exprhead.Type == ObjectTypeMap { 634 | return evalDot(EvalMap(exprhead, argobjects), root) 635 | } 636 | 637 | return evalDot(EvalSlice(exprhead, argobjects), root) 638 | } 639 | 640 | // at this point the expression must be a function call 641 | 642 | fn := exprhead.Function 643 | 644 | // builtin functions are called without evaluating the 645 | // argument syntax tree nodes, these functions can decide how to eval 646 | // arguments on their own 647 | if fn.BuiltinFunc != nil { 648 | for _, c := range root.Children[1:] { 649 | obj := Object{ 650 | Type: ObjectTypeBuiltinArgument, 651 | Value: c, 652 | } 653 | argobjects.Append(obj) 654 | } 655 | 656 | return evalDot(fn.BuiltinFunc(scope, argobjects.ToSlice()), root) 657 | } 658 | 659 | // at this point the expression must be a calling a user-defined function 660 | // all arguments are evaluated immediately 661 | for _, c := range root.Children[1:] { 662 | if c.Spread { 663 | argobjects.Join(SpreadNode(argscope, c)) 664 | } else { 665 | argobjects.Append(Eval(argscope, c)) 666 | } 667 | } 668 | 669 | patternindex, patternfound := matchPatterns(fn, argobjects) 670 | if !patternfound { return UndefinedObject() } 671 | pattern := fn.FunctionPatterns[patternindex] 672 | 673 | // calling a function with fewer arguments than required evaluates to UNDEFINED 674 | // might possibly implement automatic partial evaluation in the future 675 | if argobjects.Length < len(pattern) { 676 | return UndefinedObject() 677 | } 678 | 679 | bindArguments(exprhead, pattern, argobjects) 680 | 681 | return evalDot(Eval(exprhead.Scope, fn.FunctionBodies[patternindex]), root) 682 | } 683 | 684 | // Run: Run a Golsp program 685 | // `program`: the program to run 686 | // `dirname`: the directory of the program file 687 | // `filename`: the name of the program file 688 | // `args`: command line arguments passed to the program 689 | // this function returns the result of running the program 690 | func Run(dirname string, filename string, args []string, program string) Object { 691 | InitializeBuiltins(dirname, filename, args) 692 | result := Eval(Builtins, MakeST(Tokenize(program))) 693 | defer RuntimeWaitGroup().Wait() 694 | 695 | return result 696 | } 697 | -------------------------------------------------------------------------------- /core/list.go: -------------------------------------------------------------------------------- 1 | 2 | // List data structure 3 | 4 | package golsp 5 | 6 | type Item struct { 7 | next *Item 8 | Object Object 9 | } 10 | 11 | type List struct { 12 | First *Item 13 | Last *Item 14 | Length int 15 | branches map[int]*Item 16 | } 17 | 18 | func ListFromSlice(slice []Object) List { 19 | l := List{} 20 | for _, obj := range slice { l.Append(obj) } 21 | return l 22 | } 23 | 24 | func (l *List) at(index int) *Item { 25 | if index < 0 || index >= l.Length { return nil } 26 | if index == l.Length - 1 { return l.Last } 27 | 28 | current := l.First 29 | for i := 0; i < index; i++ { current = l.Next(current, i) } 30 | 31 | return current 32 | } 33 | 34 | func (l *List) slice(begin int, end int) List { 35 | if end <= begin { return List{} } 36 | 37 | slice := List{ 38 | First: l.at(begin), 39 | Last: l.at(end - 1), 40 | Length: end - begin, 41 | branches: make(map[int]*Item), 42 | } 43 | 44 | for index, branch := range l.branches { 45 | if index >= begin && index < end { 46 | slice.branches[index - begin] = branch 47 | } 48 | } 49 | 50 | return slice 51 | } 52 | 53 | func (l *List) sublist(begin *Item, index int) List { 54 | sublist := List{ 55 | First: begin, 56 | Last: l.Last, 57 | Length: l.Length - index, 58 | branches: make(map[int]*Item), 59 | } 60 | 61 | for i, branch := range l.branches { 62 | if i >= index { sublist.branches[i] = branch } 63 | } 64 | 65 | return sublist 66 | } 67 | 68 | func (l *List) Next(item *Item, index int) *Item { 69 | if (index >= l.Length) { return nil } 70 | 71 | branch, exists := l.branches[index] 72 | if exists { return branch } 73 | 74 | return item.next 75 | } 76 | 77 | func (l *List) ToSlice() []Object { 78 | slice := make([]Object, 0, l.Length) 79 | ptr := l.First 80 | for i := 0; i < l.Length; i++ { 81 | slice = append(slice, ptr.Object) 82 | ptr = l.Next(ptr, i) 83 | } 84 | 85 | return slice 86 | } 87 | 88 | func (l *List) Copy() List { 89 | copy := List{} 90 | slice := l.ToSlice() 91 | for _, obj := range slice { copy.Append(CopyObject(obj)) } 92 | 93 | return copy 94 | } 95 | 96 | func (l *List) Append(obj Object) { 97 | newitem := Item{Object: obj} 98 | defer func() { 99 | l.Last = &newitem 100 | l.Length++ 101 | }() 102 | 103 | if l.Length == 0 { 104 | l.First = &newitem 105 | return 106 | } 107 | 108 | if l.Last.next == nil { 109 | l.Last.next = &newitem 110 | return 111 | } 112 | 113 | if l.branches == nil { l.branches = make(map[int]*Item) } 114 | l.branches[l.Length - 1] = &newitem 115 | } 116 | 117 | func (self *List) Join(other List) { 118 | if (other.Length == 0) { return } 119 | 120 | defer func() { 121 | if (other.Length == 0) { return } 122 | 123 | for index, branch := range other.branches { 124 | self.branches[index + self.Length] = branch 125 | } 126 | self.Last = other.Last 127 | self.Length += other.Length 128 | }() 129 | 130 | if self.Length == 0 { 131 | self.First = other.First 132 | return 133 | } 134 | 135 | if self.Last.next == nil { 136 | self.Last.next = other.First 137 | return 138 | } 139 | 140 | if self.branches == nil { self.branches = make(map[int]*Item) } 141 | self.branches[self.Length - 1] = other.First 142 | } 143 | 144 | func (l *List) Index(index int) Object { 145 | if index < 0 { index += l.Length } 146 | 147 | item := l.at(index) 148 | if item == nil { return UndefinedObject() } 149 | return item.Object 150 | } 151 | 152 | func (l *List) Slice(begin int, end int) Object { 153 | if begin < 0 { begin += l.Length } 154 | if begin < 0 || begin >= l.Length { return UndefinedObject() } 155 | if end < 0 { end += l.Length } 156 | if end < 0 { return UndefinedObject() } 157 | if end > l.Length { end = l.Length } 158 | 159 | return Object{ 160 | Type: ObjectTypeList, 161 | Elements: l.slice(begin, end), 162 | } 163 | } 164 | 165 | func (l *List) SliceStep(begin int, end int, step int, sliceAll bool) Object { 166 | if begin < 0 { begin += l.Length } 167 | if begin < 0 || begin >= l.Length { return UndefinedObject() } 168 | if end < 0 && !sliceAll { end += l.Length } 169 | if end < 0 && !sliceAll { return UndefinedObject() } 170 | if end > l.Length { end = l.Length } 171 | 172 | slice := l.ToSlice() 173 | newlist := List{} 174 | for i := begin; i != end; i += step { 175 | if i >= len(slice) { break } 176 | if i < 0 { break } 177 | 178 | newlist.Append(slice[i]) 179 | } 180 | 181 | return Object{ 182 | Type: ObjectTypeList, 183 | Elements: newlist, 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /core/parser.go: -------------------------------------------------------------------------------- 1 | 2 | // Parser 3 | 4 | package golsp 5 | 6 | import ( 7 | "strings" 8 | "strconv" 9 | "unicode" 10 | "fmt" 11 | ) 12 | 13 | type OperatorType int 14 | const ( 15 | OperatorTypeSpread OperatorType = 0 16 | OperatorTypeZip OperatorType = 1 17 | OperatorTypeDot OperatorType = 2 18 | ) 19 | 20 | var Operators = []string{"...", ":", "."} 21 | var OperatorTypes = map[string]OperatorType{ 22 | "...": OperatorTypeSpread, 23 | ":": OperatorTypeZip, 24 | ".": OperatorTypeDot, 25 | } 26 | 27 | var LiteralDelimiters = map[string]string{"\"": "\"", "#": "\n"} 28 | var LiteralDelimiterTypes = map[string]STNodeType{ 29 | "\"": STNodeTypeStringLiteral, 30 | "#": STNodeTypeComment, 31 | } 32 | 33 | var TokenDelimiters = map[string]string{ 34 | "": "", 35 | "[": "]", 36 | "]": "", 37 | "{": "}", 38 | "}": "", 39 | "(": ")", 40 | ")": "", 41 | } 42 | var TokenDelimiterTypes = map[string]STNodeType{ 43 | "": STNodeTypeScope, 44 | "[": STNodeTypeExpression, 45 | "{": STNodeTypeList, 46 | "(": STNodeTypeMap, 47 | } 48 | 49 | // MakeST: construct a syntax tree from a list of tokens 50 | // `tokens`: list of tokens to parse 51 | func MakeST(tokens []string) STNode { 52 | root := STNode{Type: STNodeTypeScope} 53 | root.Children, _ = makeST(tokens[0], tokens[1:]) 54 | return root 55 | } 56 | 57 | // makeST: recursively construct a syntax tree from a list of tokens 58 | // `delim`: the leading delimeter of the current expression 59 | // `tokens`: remaining tokens to parse 60 | // this function returns a list of nodes within the current expression 61 | // and a list of remaining unparsed tokens 62 | func makeST(delim string, tokens []string) ([]STNode, []string) { 63 | nodes := make([]STNode, 0, len(tokens)) 64 | zip := false 65 | dot := false 66 | var prev *STNode 67 | var current STNode 68 | newline := false 69 | prevlength := 0 70 | i := 0 71 | 72 | for ; i < len(tokens); i++ { 73 | if tokens[i] == TokenDelimiters[delim] { return nodes, tokens[i + 1:] } 74 | 75 | if tokens[i] == "\n" { 76 | delimtype := TokenDelimiterTypes[delim] 77 | if newline && (len(nodes) - prevlength) > 1 && 78 | delimtype != STNodeTypeMap && delimtype != STNodeTypeList { 79 | node := STNode{ 80 | Type: STNodeTypeExpression, 81 | Children: make([]STNode, len(nodes[prevlength:])), 82 | } 83 | copy(node.Children, nodes[prevlength:]) 84 | nodes = nodes[:prevlength] 85 | nodes, prev, zip, dot = appendNode(nodes, node, prev, zip, dot) 86 | } 87 | newline = true 88 | prevlength = len(nodes) 89 | continue 90 | } 91 | 92 | current = STNode{ 93 | Head: tokens[i], 94 | Type: STNodeTypeIdentifier, 95 | Children: make([]STNode, 0), 96 | } 97 | 98 | // check if current token is a delimiter '[]' or '{}' 99 | // parse recursively if so 100 | delimtype, isDelimiter := TokenDelimiterTypes[current.Head] 101 | if isDelimiter { 102 | var newtokens []string 103 | current.Type = delimtype 104 | current.Children, newtokens = makeST(current.Head, tokens[i + 1:]) 105 | i = -1 106 | tokens = newtokens 107 | nodes, prev, zip, dot = appendNode(nodes, current, prev, zip, dot) 108 | continue 109 | } 110 | 111 | // check if current token is an extended literal i.e a string or comment 112 | literaltype, isLiteral := LiteralDelimiterTypes[string(current.Head[0])] 113 | if isLiteral { 114 | if literaltype == STNodeTypeComment { continue } 115 | if literaltype == STNodeTypeStringLiteral { 116 | current.Head = fmt.Sprintf("\"%s\"", normalizeString(current.Head[1:len(current.Head) - 1])) 117 | } 118 | current.Type = literaltype 119 | nodes, prev, zip, dot = appendNode(nodes, current, prev, zip, dot) 120 | continue 121 | } 122 | 123 | // check if current token is a number literal 124 | num, err := strconv.ParseFloat(current.Head, 64) 125 | if err == nil { 126 | current.Type = STNodeTypeNumberLiteral 127 | if float64(int(num)) == num { 128 | current.Head = strconv.Itoa(int(num)) 129 | } else { current.Head = fmt.Sprintf("%g", num) } 130 | nodes, prev, zip, dot = appendNode(nodes, current, prev, zip, dot) 131 | continue 132 | } 133 | 134 | // check if current token is an operator 135 | optype, isOperator := OperatorTypes[current.Head] 136 | if isOperator && len(nodes) > 0 { 137 | switch optype { 138 | case OperatorTypeSpread: prev.Spread = true 139 | case OperatorTypeZip: zip = true 140 | case OperatorTypeDot: dot = true 141 | } 142 | continue 143 | } 144 | 145 | // current token must be an identifier 146 | nodes, prev, zip, dot = appendNode(nodes, current, prev, zip, dot) 147 | } 148 | 149 | return nodes, tokens[i:] 150 | } 151 | 152 | // appendNode: append a parsed node to a list of parsed nodes 153 | // `nodes`: the list of parsed nodes 154 | // `node`: the parsed node to append to the list 155 | // `prev`: a pointer to the node that was last parsed and appended 156 | // `zip`: whether a zip operator precedes the current node 157 | // `dot`: whether a dot operator precedes the current node 158 | // this function returns the new list of nodes, a pointer to the 159 | // appended node, and values for both of the zip and dot flags 160 | // this function always returns false for zip and dot -- i could manually 161 | // reset zip and dot every time i call appendNode (see makeST), but this looks nicer 162 | func appendNode(nodes []STNode, node STNode, 163 | prev *STNode, zip bool, dot bool) ([]STNode, *STNode, bool, bool) { 164 | var addr *STNode 165 | 166 | if zip || dot { 167 | if prev != nil { 168 | if zip { 169 | prev.Zip = &node 170 | addr = prev.Zip 171 | } else { 172 | prev.Dot = &node 173 | addr = prev.Dot 174 | } 175 | } 176 | } else { 177 | nodes = append(nodes, node) 178 | addr = &nodes[len(nodes) - 1] 179 | } 180 | 181 | return nodes, addr, false, false 182 | } 183 | 184 | // normalizeString: Replace esacped escape sequences with actual escape 185 | // sequences in a string 186 | // `str`: the string 187 | // this function returns the normalized string 188 | func normalizeString(str string) string { 189 | str = strings.Replace(str, "\\n", "\n", -1) 190 | str = strings.Replace(str, "\\\"", "\"", -1) 191 | 192 | return str 193 | } 194 | 195 | // parseLiteral: parse an extended literal, i.e a string or comment 196 | // `delimiter`: leading delimiter of literal, either '"' or '#' 197 | // `input`: list of unparsed characters following delimiter 198 | // this function returns the number of characters it has parsed 199 | // and a literal token 200 | func parseLiteral(delimiter string, input []rune) (int, string) { 201 | escape := '\\' 202 | str := "" 203 | i := 0 204 | 205 | for ; i < len(input); i++ { 206 | if input[i] == escape { 207 | str += string(input[i]) 208 | i++ 209 | str += string(input[i]) 210 | continue 211 | } 212 | 213 | if string(input[i]) == LiteralDelimiters[delimiter] { 214 | str += LiteralDelimiters[delimiter] 215 | i++ 216 | break 217 | } 218 | 219 | str += string(input[i]) 220 | } 221 | 222 | return i, str 223 | } 224 | 225 | // matchOperator: check if a list of characters contains an operator 226 | // and find the correct operator if so 227 | // `runes`: list of characters 228 | // `index`: index at which to begin searching runes for an operator 229 | // this function returns the index of the found operator in `Operators` 230 | // (defined above) or -1 if runes does not contain an operator immediately 231 | // after index 232 | func matchOperator(runes []rune, index int) int { 233 | matchindex := -1 234 | matchscore := 0 235 | r := runes[index] 236 | 237 | for i, op := range Operators { 238 | score := 0 239 | if r != rune(op[0]) { continue } 240 | if index + len(op) > len(runes) { continue } 241 | 242 | opstr := string(runes[index:index + len(op)]) 243 | if op == opstr { score = len(op) } 244 | 245 | if score > matchscore { 246 | matchscore = score 247 | matchindex = i 248 | } 249 | } 250 | 251 | return matchindex 252 | } 253 | 254 | // Tokenize: tokenize a string 255 | // `input`: the string to tokenize 256 | // this function returns a list of tokens 257 | func Tokenize(input string) []string { 258 | input = strings.TrimSpace(input) 259 | runes := []rune(input) 260 | token := "" 261 | tokens := []string{token, "\n"} 262 | 263 | for i := 0; i < len(runes); i++ { 264 | r := runes[i] 265 | 266 | if r == '\n' { 267 | if len(token) > 0 { 268 | tokens = append(tokens, token) 269 | token = "" 270 | } 271 | 272 | tokens = append(tokens, "\n") 273 | continue 274 | } 275 | 276 | end, literal := LiteralDelimiters[string(r)] 277 | if literal { 278 | if len(token) > 0 { 279 | tokens = append(tokens, token) 280 | token = "" 281 | } 282 | 283 | len, str := parseLiteral(string(r), runes[i + 1:]) 284 | i += len 285 | tokens = append(tokens, string(r) + str) 286 | if end == "\n" { tokens = append(tokens, end) } 287 | continue 288 | } 289 | 290 | opindex := matchOperator(runes, i) 291 | if opindex != -1 { 292 | op := Operators[opindex] 293 | i += len(op) - 1 294 | 295 | // weird hack to get dot operator to play nicely with floating-point numbers 296 | isNumber := false 297 | if op == "." { 298 | _, err := strconv.ParseFloat(token, 64) 299 | isNumber = err == nil 300 | } 301 | 302 | if op != "." || (!isNumber) { 303 | if len(token) > 0 { 304 | tokens = append(tokens, token) 305 | token = "" 306 | } 307 | 308 | tokens = append(tokens, op) 309 | continue 310 | } 311 | } 312 | 313 | _, delimiter := TokenDelimiters[string(r)] 314 | if !delimiter && !unicode.IsSpace(r) { 315 | token += string(r) 316 | continue 317 | } 318 | 319 | if len(token) > 0 { 320 | tokens = append(tokens, token) 321 | } 322 | 323 | token = "" 324 | if delimiter { 325 | tokens = append(tokens, string(r)) 326 | } 327 | } 328 | 329 | if len(token) > 0 { tokens = append(tokens, token) } 330 | tokens = append(tokens, "\n", "") 331 | 332 | return tokens 333 | } 334 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | 2 | // Debugging functions 3 | 4 | package main 5 | 6 | import ( 7 | "strconv" 8 | "strings" 9 | . "github.com/ajaymt/golsp/core" 10 | ) 11 | 12 | func PrintElements(list Object) string { 13 | str := "" 14 | for _, elem := range list.Elements.ToSlice() { 15 | if elem.Type == ObjectTypeList { 16 | str += "{ " + PrintElements(elem) + " }" 17 | } 18 | 19 | str += " " + elem.Value.Head 20 | } 21 | 22 | return str 23 | } 24 | 25 | func PrintST(root STNode) string { 26 | str := "" 27 | 28 | str += "\nHead: \"" + root.Head + 29 | "\"\nType: " + strconv.Itoa(int(root.Type)) + 30 | "\nSpread: " + strconv.FormatBool(root.Spread) 31 | 32 | if len(root.Children) > 0 { 33 | str += "\nChildren: (" 34 | for _, child := range root.Children { 35 | childstr := PrintST(child) 36 | lines := strings.Split(childstr, "\n") 37 | for i := 0; i < len(lines); i++ { 38 | lines[i] = " " + lines[i] 39 | } 40 | 41 | str += strings.Join(lines, "\n") 42 | } 43 | str += "\n)," 44 | } 45 | 46 | if root.Zip != nil { 47 | str += "\nZip: (" 48 | childstr := PrintST(*root.Zip) 49 | lines := strings.Split(childstr, "\n") 50 | for i := 0; i < len(lines); i++ { 51 | lines[i] = " " + lines[i] 52 | } 53 | str += strings.Join(lines, "\n") 54 | str += "\n)," 55 | } 56 | 57 | if root.Dot != nil { 58 | str += "\nDot: (" 59 | childstr := PrintST(*root.Dot) 60 | lines := strings.Split(childstr, "\n") 61 | for i := 0; i < len(lines); i++ { 62 | lines[i] = " " + lines[i] 63 | } 64 | str += strings.Join(lines, "\n") 65 | str += "\n)," 66 | } 67 | 68 | return str 69 | } 70 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | golsp: *.go core/*.go stdlib/**/* 3 | make -C stdlib/types 4 | make -C stdlib/os 5 | go build -o golsp *.go 6 | 7 | .PHONY: clean 8 | clean: 9 | rm -f golsp 10 | rm -f stdlib/**/*.so 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Golsp 3 | (pronounced "Go-lisp") 4 | ```python 5 | printf "Hello, world!\n" 6 | ``` 7 | 8 | Golsp is a simple, interpreted lisp-like programming language. It is not intended to be fast, efficient or particularly sophisticated -- I wrote this as an exercise in programming language design, and for fun. As such, the language itself (`core/`) is feature complete (aside from a few trivial `TODO`s) and all that remains unfinished is the standard library. 9 | 10 | 11 | 12 | **Table of contents** 13 | - [Syntax and features](#syntax_and_features) 14 | - [Lists](#lists) 15 | - [Maps](#maps) 16 | - [Spread operator](#spread_operator) 17 | - [Pattern matching](#pattern_matching) 18 | - [Scopes, modules and concurrency](#scopes_modules_and_concurrency) 19 | - [Installation](#installation) 20 | - [Usage](#usage) 21 | - [Contributing](#contributing) 22 | - [Author(s)](#authors) 23 | 24 | ## Syntax and features 25 | On the surface, Golsp looks like an odd dialect of Lisp with some superficial syntactic changes: 26 | ```python 27 | # comments begin with '#' 28 | # expressions are enclosed by '[]' 29 | [def [double x] [* x 2]] 30 | [double 4] # => 8 31 | ``` 32 | 33 | My choice of brackets is not entirely arbitrary: `[` and `]` require very little effort to type, and it is an homage to Objective-C, which is one of my favourite languages. If you really don't like the look of dozens of nested square brackets (even I, a seasoned Golsper, sometimes tire of them), you can simply replace them with newlines. 34 | ```python 35 | # this: 36 | printf "%v\n" [function 37 | argument quux 38 | fuzz cat 39 | foo 40 | bar baz] 41 | 42 | # is automatically translated into this: 43 | [printf "%v\n" [function 44 | [argument quux] 45 | [fuzz cat] 46 | foo 47 | bar baz]] 48 | 49 | # note that the 'foo' was not wrapped in an expression -- Golsp 50 | # does not automatically expression-ify single-token lines because you may 51 | # not intend to call them as functions 52 | # 'bar baz' was also not wrapped in an expression, because the surrounding 53 | # expression ends on the same line 54 | # Golsp will not wrap lines that are inside lists or maps (more on those later) 55 | # in expressions 56 | ``` 57 | 58 | Golsp is only similar to Lisp at a superficial level. At heart, it is much more like a stripped-down, simplified, functional version of Javascript. Golsp is a strong-but-dynamically typed language with two primitive types: 59 | ```python 60 | 1 1.1 -3.5 1200 # numbers 61 | "hello" "foo" "bar" "baz" # strings 62 | # there is no boolean type -- Golsp uses numbers instead 63 | 64 | # the special 'undefined' identifier has no value and evaluates to itself 65 | undefined 66 | ``` 67 | 68 | Variables and constants (including functions) are declared with `def` and `const`. `lambda` creates an anonymous function. 69 | ```python 70 | def x 1 # x is a number 71 | def [square n] [* n n] # square is a function 72 | def square [lambda [n] [* n n]] # this is effectively the same as the previous statement 73 | const a "hello" # 'a' is a string constant 74 | def x 2 # this works 75 | def a 3 # this does not 76 | ``` 77 | 78 | Golsp has two simple built-in data structures. 79 | 80 | ### Lists 81 | ```python 82 | def mylist { 5 6 7 "a" + "c" } 83 | ``` 84 | 85 | Calling Golsp's lists 'lists' is somewhat misleading since they are actually immutable. Lists can be indexed and sliced in a variety of ways: 86 | ```python 87 | # providing a single 'argument' indexes the list 88 | mylist 0 # => 5 89 | # negative indices count backwards from the end of the list 90 | mylist -1 # => "c" 91 | 92 | # two arguments slice the list -- begin (inclusive) and end (exclusive) 93 | mylist 1 5 # => { 6 7 "a" + } 94 | mylist 0 -1 # => { 5 6 7 "a" + } 95 | # 'undefined' slices until the end of the list 96 | mylist 2 undefined # => { 7 "a" + "c" } 97 | 98 | # three arguments slice the list and skip elements -- begin, end and step 99 | mylist 0 undefined 2 # => { 5 7 + } 100 | # negative step reverses the list 101 | mylist -1 0 -1 # => { "c" + "a" 7 6 } 102 | mylist -2 undefined -2 # => { + 7 5 } 103 | ``` 104 | 105 | Strings can also be indexed and sliced like lists. 106 | 107 | Golsp's parser will not automatically convert newlines to expression delimiters inside lists. This means that 108 | ```python 109 | { 110 | a b c 111 | } 112 | ``` 113 | is **NOT** evaluated as 114 | ```python 115 | { 116 | [a b c] 117 | } 118 | ``` 119 | since tokens inside lists are usually meant to be separate elements and not expressions. 120 | 121 | ### Maps 122 | ```python 123 | def mymap ( 124 | "a": 1 125 | "foo": "bar" 126 | 12: "quux" 127 | "plus": + 128 | ) 129 | ``` 130 | 131 | Maps map literals (i.e strings and numbers) to arbitrary values. Like lists, maps are immutable. They are also ordered -- key-value pairs are inserted in the order they are specified. 132 | ```python 133 | # single 'arguments' lookup a key 134 | mymap "a" # => 1 135 | mymap 12 # => "quux" 136 | 137 | # multiple keys produce a list of values 138 | mymap "foo" "plus" 12 # => { "bar" + "quux" } 139 | 140 | # repeating a key overwrites its previous value 141 | ( "a":1 "a":2 ) # => ( "a":2 ) 142 | ``` 143 | 144 | String keys can also be evaluated with the special 'dot' syntax: 145 | ```python 146 | # this is equivalent to [mymap "a"] 147 | mymap.a # => 1 148 | mymap.plus # => + 149 | ``` 150 | 151 | As in lists, tokens bounded by newlines are not wrapped in expressions. 152 | ```python 153 | ( 154 | "a": "b" 155 | ) 156 | 157 | # is NOT evaluated as 158 | 159 | ( 160 | ["a": "b"] 161 | ) 162 | ``` 163 | 164 | ### Spread operator (`...`) 165 | The spread operator `...` takes a list, map or string and distributes its contents into the surrounding expression. 166 | ```python 167 | def mylist { 1 2 3 } 168 | printf "%v %v %v\n" mylist... # => prints "1 2 3" 169 | 170 | def str "abc" 171 | printf "%v %v %v\n" str... # => prints "a b c" 172 | ``` 173 | 174 | The spread operator makes typical list operations like appending and inserting very simple. 175 | ```python 176 | def [append list value] { list... value } 177 | def [insert list index value] { [list 0 index]... value [list index undefined]... } 178 | def [join list1 list2] { list1... list2... } 179 | 180 | append { 1 2 3 } 4 # => { 1 2 3 4 } 181 | insert { 1 2 4 5 } 2 3 # => { 1 2 3 4 5 } 182 | join { 1 2 3 } { 4 5 6 } # => { 1 2 3 4 5 6 } 183 | ``` 184 | 185 | Lists can also be evaluated as expressions. 186 | ```python 187 | def expr { + 1 1 } 188 | [expr...] # => 2 189 | ``` 190 | 191 | Unlike lists, spreading a map produces its keys. Maps are ordered, so they spread to their keys in the same order every time. 192 | ```python 193 | def mymap ( "a":1 "b":2 "c":3 ) 194 | printf "%v %v %v\n" mymap... # => prints "a b c" 195 | printf "%v %v %v\n" [mymap mymap...]... # => prints "1 2 3" 196 | ``` 197 | 198 | The spread operator can 'zip' keys and values together when used inside a map. 199 | ```python 200 | def keys { "foo" "bar" "baz" } 201 | def values { 1 2 3 4 } 202 | def map ( keys... : values... ) # => ( "foo":1 "bar":2 "baz":3 ) 203 | 204 | # since maps are ordered, constructing new maps from old ones is simple 205 | def map2 ( 206 | map... : [map map...]... 207 | "foo": 5 208 | "quux": "z00t" 209 | ) # => ( "foo":5 "bar":2 "baz":3 "quux":"z00t" ) 210 | ``` 211 | 212 | ### Pattern matching 213 | Functions in Golsp are produced by the `def`, `const` and `lambda` builtins. 214 | ```python 215 | def [square n] [* n n] 216 | const [double n] [* 2 n] 217 | def increment [lambda [x] [+ 1 x]] 218 | ``` 219 | 220 | As in many other functional languages, Golsp features pattern matching. Patterns function as implicit 'switch' statements -- arguments are compared against them in the order they are defined until a perfect match is found. 221 | ```python 222 | # it is important to define [factorial 0] before [factorial n] -- otherwise 223 | # this function never terminates 224 | def [factorial 0] 1 225 | [def [factorial n] 226 | * n [factorial [- n 1]] 227 | ] 228 | 229 | factorial 6 # => 720 230 | ``` 231 | 232 | Patterns can also automatically de-structure data and 'gather' it (the opposite of spreading). This is best illustrated with an example: 233 | ```python 234 | # 'len' finds the length of a list 235 | def [len {}] 0 236 | def [len { head tail... }] [+ 1 [len tail]] 237 | len { 1 2 3 4 } # => 4 238 | 239 | # functions can be variadic thanks to patterns 240 | def [count args...] [len args] 241 | count "a" "b" "c" 1 2 3 # => 6 242 | 243 | # patterns can also match against and extract data from maps 244 | def [greet ( "name":name rest... )] [sprintf "Hello, %v!" name] 245 | def [greet ( keys... )] "Please introduce yourself!" 246 | def [greet _] "You're not a map!" 247 | greet ( "profession":"chef" "name":"Gordon Ramsay" ) # => Hello, Gordon Ramsay! 248 | greet ( "foo":"bar" ) # => Please introduce yourself! 249 | greet 12 # => You're not a map! 250 | ``` 251 | 252 | Pattern matching works well with the builtin `when` function and `types` module to provide simple and flexible polymorphism: 253 | ```python 254 | const types [require "stdlib/types.golsp"] # basic type checking 255 | const _ [require "stdlib/tools.golsp"] # map, filter and other higher-order functions 256 | 257 | def [typeof x] [when 258 | [types.isNumber x]: "number" 259 | [types.isString x]: "string" 260 | [types.isFunction x]: "function" 261 | [types.isList x]: [sprintf "list(%v)" [typeof x...]] 262 | 1: "map" 263 | ] 264 | def [typeof xs...] [_.map typeof xs] 265 | 266 | typeof 1 # => "number" 267 | typeof 2 "a" typeof # => { "number" "string" "function" } 268 | typeof { 1 2 3 "a" "b" "c" } # => "list({number number number string string string })" 269 | ``` 270 | 271 | ### Scopes, modules and concurrency 272 | Functions in Golsp are evaluated in their own scopes -- they cannot re-bind symbols defined in outer scopes. Golsp also has a `do` builtin function that defines a scope of its own. 273 | ```python 274 | [do 275 | def name "Ajay" 276 | printf "hello %v\n" name 277 | ] 278 | ``` 279 | 280 | `do` blocks evaluate to the result of the last statement in the block. 281 | ```python 282 | def [f x] [do 283 | def doubled [* 2 x] 284 | def squared [* doubled doubled] 285 | def halved [/ squared 2] 286 | + 1 halved 287 | ] 288 | 289 | f 2 # => 9 290 | ``` 291 | 292 | Since `do` blocks don't have side-effects, it is safe to execute them concurrently. This is what the builtin `go` function does. 293 | ```python 294 | def x 1 295 | [go 296 | def x 2 297 | sleep 1000 298 | printf "world %v\n" x 299 | ] 300 | sleep 500 301 | printf "hello %v " x 302 | # prints "hello 1 world 2" 303 | ``` 304 | Golsp's `go` blocks are a thin layer atop Go's goroutines, which means they're lightweight and efficient. 305 | 306 | Files are effectively the same as `do` blocks -- they define a scope, and they 'evaluate' to the result of the last statement. This is the basis of Golsp's module system (which is actually almost too simple to be a 'module system'). 307 | ```python 308 | ##### a.golsp ##### 309 | def [double x] [* x 2] 310 | def [square x] [* x x] 311 | 312 | # this map gets exported since it is the last statement 313 | ( 314 | "double": double 315 | "square": square 316 | ) 317 | 318 | ##### b.golsp ##### 319 | # the 'require' function evaluates and imports a file 320 | # the specified path is resolved relative to the current file ("b.golsp" in this case) 321 | const a [require "a.golsp"] 322 | a.double 3 # => 6 323 | a.square 9 # => 81 324 | ``` 325 | 326 | `require` can also import standard library modules -- it will do so if the provided path begins with `stdlib/` (see Installation and `GOLSPPATH` below.) 327 | 328 | ## Installation 329 | Unfortunately, Golsp only supports Linux and macOS at the moment. This installation process assumes that you have GNU make and Go installed, and that your `GOPATH` is set up correctly. 330 | 331 | ```sh 332 | go get github.com/ajaymt/golsp 333 | cd $GOPATH/src/github.com/ajaymt/golsp 334 | make 335 | go install 336 | export GOLSPPATH="$GOPATH/src/github.com/ajaymt/golsp" # add this to your dotfile 337 | ``` 338 | 339 | ## Usage 340 | ```sh 341 | golsp [file] # execute 'file' 342 | golsp - # read from stdin 343 | ``` 344 | 345 | The CLI will eventually get better. 346 | 347 | ## Contributing 348 | Yes please! I will merge your code as long as it is: 349 | - tested. A simple test will do -- I haven't written any comprehensive unit tests yet. 350 | - readable. I'm not very picky about style -- I just like to follow a consistent naming convention. But please indent with tabs and be generous with whitespace. 351 | - (reasonably) fast. Do not sacrifice a lot of generality and readability for speed, but don't write bubblesort either. 352 | 353 | Here are some things I haven't done yet: 354 | - implemented errors (syntax and semantic errors) or error handling (`stdlib/assert.golsp` is supposed to throw errors and halt the program when assertions fail) 355 | - written tests 356 | - finished the CLI 357 | - finished the builtin string formatter (see `formatStr` in `core/builtins.go`) 358 | - finished the standard library (! high priority !) 359 | - written documentation (! higher priority !) 360 | - other miscellaneous `TODO`s in the codebase 361 | 362 | Contributing is not limited to writing code. If you find a bug, want a feature or just want to discuss some ideas, please raise an issue! 363 | 364 | ## Author(s) 365 | - Ajay Tatachar (ajaymt2@illinois.edu) 366 | -------------------------------------------------------------------------------- /stdlib/.gitignore: -------------------------------------------------------------------------------- 1 | */*.so -------------------------------------------------------------------------------- /stdlib/assert.golsp: -------------------------------------------------------------------------------- 1 | 2 | const os [require "./os.golsp"] 3 | const _ [require "./tools.golsp"] 4 | 5 | 6 | const [panic msg] [do 7 | os.write os.stderr [sprintf "%v" msg] 8 | os.exit 1 9 | ] 10 | 11 | 12 | def [fmt { obj }] [fmt obj] 13 | [def [fmt { contents... }] 14 | _.join " " { "[" contents... "]" } 15 | ] 16 | def [fmt obj] [sprintf "%v" obj] 17 | 18 | 19 | const [assert stmt...] [do 20 | const result [if [== 1 [_.len stmt]] [stmt 0] [stmt...]] 21 | [if result result 22 | panic [sprintf "failed assertion: %v\nfound: %v\n" [fmt stmt] result] 23 | ] 24 | ] 25 | 26 | 27 | # exports 28 | assert 29 | -------------------------------------------------------------------------------- /stdlib/os.golsp: -------------------------------------------------------------------------------- 1 | 2 | const base [require "./os/os.so"] 3 | const types [require "./types.golsp"] 4 | const isn types.isNumber 5 | const iss types.isString 6 | 7 | 8 | const [open fname] [when [iss fname]: [base.open fname]] 9 | const [create fname] [when [iss fname]: [base.create fname]] 10 | 11 | 12 | const [remove fname] [when [iss fname]: [base.remove fname]] 13 | const [removeAll fname] [when [iss fname]: [base.removeAll fname]] 14 | 15 | 16 | const [mkdir fname] [when [iss fname]: [base.mkdir fname]] 17 | 18 | 19 | const [read index n] [when 20 | [* [isn index] [isn n]]: [base.read index n] 21 | ] 22 | const [readAll index] [when [isn index]: [base.readAll index]] 23 | const [readUntil index delim] [when 24 | [* [isn index] [iss delim]]: [base.readUntil index delim] 25 | ] 26 | 27 | 28 | const [write index str] [when 29 | [* [isn index] [iss str]]: [base.write index str] 30 | ] 31 | 32 | 33 | def [seek index pos whence] [when 34 | [* [isn index] [isn pos] [isn whence]]: [base.seek index pos whence] 35 | ] 36 | def [seek index pos] [seek index pos 0] 37 | const seek seek 38 | 39 | 40 | const [stat filename] [when [iss filename]: [base.stat filename]] 41 | 42 | 43 | const [readDir dirname] [when [iss dirname]: [base.readDir dirname]] 44 | 45 | 46 | const [exit n] [when [isn n]: [base.exit n]] 47 | 48 | 49 | # exports 50 | ( 51 | "stdin": base.stdin 52 | "stdout": base.stdout 53 | "stderr": base.stderr 54 | "open": open 55 | "create": create 56 | "remove": remove 57 | "removeAll": removeAll 58 | "mkdir": mkdir 59 | "read": read 60 | "readAll": readAll 61 | "readUntil": readUntil 62 | "write": write 63 | "seek": seek 64 | "stat": stat 65 | "readDir": readDir 66 | "exit": exit 67 | ) 68 | -------------------------------------------------------------------------------- /stdlib/os/makefile: -------------------------------------------------------------------------------- 1 | 2 | all: os.go 3 | go build -buildmode=plugin -o os.so os.go 4 | -------------------------------------------------------------------------------- /stdlib/os/os.go: -------------------------------------------------------------------------------- 1 | 2 | package main 3 | 4 | import ( 5 | "os" 6 | "io" 7 | "io/ioutil" 8 | "bufio" 9 | g "github.com/ajaymt/golsp/core" 10 | ) 11 | 12 | type file struct { 13 | file *os.File 14 | reader *bufio.Reader 15 | writer *bufio.Writer 16 | } 17 | 18 | var openFiles = []file{ 19 | file{file: os.Stdin, reader: bufio.NewReader(os.Stdin), writer: nil}, 20 | file{file: os.Stdout, reader: nil, writer: bufio.NewWriter(os.Stdout)}, 21 | file{file: os.Stderr, reader: nil, writer: bufio.NewWriter(os.Stderr)}, 22 | } 23 | 24 | func cropen(scope g.Scope, args []g.Object, create bool) g.Object { 25 | arguments := g.EvalArgs(scope, args) 26 | filename, _ := g.ToString(arguments[0]) 27 | 28 | mode := os.O_RDWR 29 | if create { mode = os.O_RDWR | os.O_CREATE } 30 | f, err := os.OpenFile(filename, mode, 0644) 31 | if err != nil { return g.UndefinedObject() } 32 | 33 | reader := bufio.NewReader(f) 34 | writer := bufio.NewWriter(f) 35 | openFiles = append(openFiles, file{file: f, reader: reader, writer: writer}) 36 | 37 | return g.NumberObject(float64(len(openFiles) - 1)) 38 | } 39 | 40 | func open(s g.Scope, a []g.Object) g.Object { return cropen(s, a, false) } 41 | func create(s g.Scope, a []g.Object) g.Object { return cropen(s, a, true) } 42 | 43 | func rm(scope g.Scope, args []g.Object, all bool) g.Object { 44 | arguments := g.EvalArgs(scope, args) 45 | path, _ := g.ToString(arguments[0]) 46 | 47 | rmf := os.Remove 48 | if all { rmf = os.RemoveAll } 49 | err := rmf(path) 50 | if err != nil { return g.NumberObject(0.0) } 51 | 52 | return g.NumberObject(1.0) 53 | } 54 | 55 | func remove(s g.Scope, a []g.Object) g.Object { return rm(s, a, false) } 56 | func removeAll(s g.Scope, a []g.Object) g.Object { return rm(s, a, true) } 57 | 58 | func mkdir(scope g.Scope, args []g.Object) g.Object { 59 | arguments := g.EvalArgs(scope, args) 60 | path, _ := g.ToString(arguments[0]) 61 | 62 | err := os.MkdirAll(path, 0755) 63 | if err != nil { return g.NumberObject(0.0) } 64 | 65 | return g.NumberObject(1.0) 66 | } 67 | 68 | func read(scope g.Scope, args []g.Object) g.Object { 69 | arguments := g.EvalArgs(scope, args) 70 | indexf, _ := g.ToNumber(arguments[0]) 71 | nf, _ := g.ToNumber(arguments[1]) 72 | index, n := int(indexf), int(nf) 73 | if index < 0 || index >= len(openFiles) { return g.UndefinedObject() } 74 | if n < 0 { return g.UndefinedObject() } 75 | 76 | readwriter := openFiles[index] 77 | if readwriter.reader == nil { return g.UndefinedObject() } 78 | 79 | bytes := make([]byte, n) 80 | _, err := readwriter.reader.Read(bytes) 81 | if err != nil && err != io.EOF { return g.UndefinedObject() } 82 | 83 | return g.StringObject(string(bytes)) 84 | } 85 | 86 | func readAll(scope g.Scope, args []g.Object) g.Object { 87 | arguments := g.EvalArgs(scope, args) 88 | indexf, _ := g.ToNumber(arguments[0]) 89 | index := int(indexf) 90 | if index < 0 || index >= len(openFiles) { return g.UndefinedObject() } 91 | 92 | readwriter := openFiles[index] 93 | if readwriter.reader == nil { return g.UndefinedObject() } 94 | 95 | bytes := make([]byte, 0) 96 | b, err := readwriter.reader.ReadByte() 97 | for ; err == nil; b, err = readwriter.reader.ReadByte() { 98 | bytes = append(bytes, b) 99 | } 100 | 101 | if err != io.EOF { return g.UndefinedObject() } 102 | 103 | return g.StringObject(string(bytes)) 104 | } 105 | 106 | func readUntil(scope g.Scope, args []g.Object) g.Object { 107 | arguments := g.EvalArgs(scope, args) 108 | indexf, _ := g.ToNumber(arguments[0]) 109 | index := int(indexf) 110 | delim, _ := g.ToString(arguments[1]) 111 | if index < 0 || index >= len(openFiles) || len(delim) == 0 { 112 | return g.UndefinedObject() 113 | } 114 | 115 | readwriter := openFiles[index] 116 | if readwriter.reader == nil { return g.UndefinedObject() } 117 | 118 | bytes, err := readwriter.reader.ReadBytes(delim[0]) 119 | if err != nil && err != io.EOF { return g.UndefinedObject() } 120 | 121 | return g.StringObject(string(bytes[:len(bytes) - 1])) 122 | } 123 | 124 | func write(scope g.Scope, args []g.Object) g.Object { 125 | arguments := g.EvalArgs(scope, args) 126 | str, _ := g.ToString(arguments[1]) 127 | indexf, _ := g.ToNumber(arguments[0]) 128 | index := int(indexf) 129 | if index < 0 || index >= len(openFiles) { return g.UndefinedObject() } 130 | 131 | readwriter := openFiles[index] 132 | if readwriter.writer == nil { return g.UndefinedObject() } 133 | 134 | nwritten, err := readwriter.writer.WriteString(str) 135 | if err != nil { return g.UndefinedObject() } 136 | 137 | err = readwriter.writer.Flush() 138 | if err != nil { return g.UndefinedObject() } 139 | 140 | return g.NumberObject(float64(nwritten)) 141 | } 142 | 143 | func seek(scope g.Scope, args []g.Object) g.Object { 144 | arguments := g.EvalArgs(scope, args) 145 | indexf, _ := g.ToNumber(arguments[0]) 146 | index := int(indexf) 147 | if index < 0 || index >= len(openFiles) { return g.UndefinedObject() } 148 | posf, _ := g.ToNumber(arguments[1]) 149 | pos := int64(posf) 150 | if pos < 0 { return g.UndefinedObject() } 151 | whencef, _ := g.ToNumber(arguments[2]) 152 | whence := int(whencef) 153 | if whence < 0 || whence > 2 { return g.UndefinedObject() } 154 | 155 | file := openFiles[index] 156 | newpos, err := file.file.Seek(pos, whence) 157 | if err != nil { return g.UndefinedObject() } 158 | 159 | return g.NumberObject(float64(newpos)) 160 | } 161 | 162 | func fileInfoToObject(fi os.FileInfo) g.Object { 163 | isDir := 0.0 164 | if fi.IsDir() { isDir = 1.0 } 165 | 166 | return g.MapObject(map[string]g.Object{ 167 | "name": g.StringObject(fi.Name()), 168 | "size": g.NumberObject(float64(fi.Size())), 169 | "isDir": g.NumberObject(isDir), 170 | }) 171 | } 172 | 173 | func stat(scope g.Scope, args []g.Object) g.Object { 174 | arguments := g.EvalArgs(scope, args) 175 | filename, _ := g.ToString(arguments[0]) 176 | 177 | fileinfo, err := os.Stat(filename) 178 | if err != nil { return g.UndefinedObject() } 179 | 180 | return fileInfoToObject(fileinfo) 181 | } 182 | 183 | func readDir(scope g.Scope, args []g.Object) g.Object { 184 | arguments := g.EvalArgs(scope, args) 185 | dirname, _ := g.ToString(arguments[0]) 186 | 187 | dirinfo, err := ioutil.ReadDir(dirname); 188 | if err != nil { return g.UndefinedObject() } 189 | 190 | contents := g.List{} 191 | for _, file := range dirinfo { 192 | contents.Append(fileInfoToObject(file)) 193 | } 194 | 195 | // TODO write a list-object contructor? 196 | return g.Object{ 197 | Type: g.ObjectTypeList, 198 | Elements: contents, 199 | } 200 | } 201 | 202 | func exit(scope g.Scope, args []g.Object) g.Object { 203 | arguments := g.EvalArgs(scope, args) 204 | n, _ := g.ToNumber(arguments[0]) 205 | os.Exit(int(n)) 206 | return g.UndefinedObject() 207 | } 208 | 209 | var Exports = g.MapObject(map[string]g.Object{ 210 | "stdin": g.NumberObject(0.0), 211 | "stdout": g.NumberObject(1.0), 212 | "stderr": g.NumberObject(2.0), 213 | "open": g.BuiltinFunctionObject("open", open), 214 | "create": g.BuiltinFunctionObject("create", create), 215 | "remove": g.BuiltinFunctionObject("remove", remove), 216 | "removeAll": g.BuiltinFunctionObject("removeAll", removeAll), 217 | "mkdir": g.BuiltinFunctionObject("mkdir", mkdir), 218 | "read": g.BuiltinFunctionObject("read", read), 219 | "readAll": g.BuiltinFunctionObject("readAll", readAll), 220 | "readUntil": g.BuiltinFunctionObject("readUntil", readUntil), 221 | "write": g.BuiltinFunctionObject("write", write), 222 | "seek": g.BuiltinFunctionObject("seek", seek), 223 | "stat": g.BuiltinFunctionObject("stat", stat), 224 | "readDir": g.BuiltinFunctionObject("readDir", readDir), 225 | "exit": g.BuiltinFunctionObject("exit", exit), 226 | }) 227 | -------------------------------------------------------------------------------- /stdlib/tools.golsp: -------------------------------------------------------------------------------- 1 | 2 | const types [require "./types.golsp"] 3 | 4 | 5 | def [len {}] 0 6 | def [len { _ tail... }] [+ 1 [len tail]] 7 | def [len s] [when [types.isString s]: [len { s... }]] 8 | const len len 9 | 10 | 11 | def [map f {}] {} 12 | def [map f { head tail... }] { [f... head] [map f tail]... } 13 | def [map f s] [when [types.isString s]: [map f { s... }]] 14 | const map map 15 | 16 | 17 | def [filter f {}] {} 18 | [def [filter f { head tail... }] 19 | if [f... head] { head [filter f tail]... } { [filter f tail]... } 20 | ] 21 | def [filter f s] [when [types.isString s]: [filter f { s... }]] 22 | const filter filter 23 | 24 | 25 | [def [range begin end step] 26 | [when 27 | [== begin end]: {} 28 | [* [< begin end] [< step 0]]: {} 29 | [* [> begin end] [> step 0]]: {} 30 | 1: { begin [range [+ begin step] end step]... } 31 | ] 32 | ] 33 | [def [range begin end] 34 | [when 35 | [< end begin]: [range begin end -1] 36 | 1: [range begin end 1] 37 | ] 38 | ] 39 | def [range n] [range 0 n] 40 | const range range 41 | 42 | 43 | def [compose input {}] input 44 | def [compose input { head tail... }] [compose [head... input] tail] 45 | def [compose input functions...] [compose input functions] 46 | const compose compose 47 | 48 | 49 | def [join _ {}] "" 50 | def [join sep { head }] head 51 | def [join sep { head tail... }] [sprintf "%v%v%v" head sep [join sep tail]] 52 | const join join 53 | 54 | 55 | def [split f {}] {} 56 | def [split f { head tail... }] [do 57 | const rest [split f tail] 58 | [if [f... head] 59 | { {} rest... } 60 | { { head [rest 0]... } [rest 1 undefined]... } 61 | ] 62 | ] 63 | def [split f s] [when 64 | [types.isString s]: [map { join "" } [split f { s... }]] 65 | ] 66 | const split split 67 | 68 | # TODO foldl foldr 69 | 70 | # exports 71 | ( 72 | "len": len 73 | "map": map 74 | "filter": filter 75 | "range": range 76 | "compose": compose 77 | "join": join 78 | "split": split 79 | ) 80 | -------------------------------------------------------------------------------- /stdlib/types.golsp: -------------------------------------------------------------------------------- 1 | 2 | const base [require "./types/types.so"] 3 | const [parseNumber x] [when [base.isString x]: [base.parseNumber x] [base.isNumber x]: x] 4 | 5 | # exports 6 | ( 7 | base... : [base base...]... 8 | "parseNumber": parseNumber 9 | ) 10 | -------------------------------------------------------------------------------- /stdlib/types/makefile: -------------------------------------------------------------------------------- 1 | 2 | all: types.go 3 | go build -buildmode=plugin -o types.so types.go 4 | -------------------------------------------------------------------------------- /stdlib/types/types.go: -------------------------------------------------------------------------------- 1 | 2 | package main 3 | 4 | import ( 5 | "strconv" 6 | g "github.com/ajaymt/golsp/core" 7 | ) 8 | 9 | func typeCheck(objectType g.ObjectType, nodeType g.STNodeType) g.BuiltinFunction { 10 | return func (scope g.Scope, args []g.Object) g.Object { 11 | arguments := g.EvalArgs(scope, args) 12 | if len(arguments) < 1 { 13 | return g.UndefinedObject() 14 | } 15 | if arguments[0].Type != objectType { 16 | return g.NumberObject(0.0) 17 | } 18 | if objectType == g.ObjectTypeLiteral && arguments[0].Value.Type != nodeType { 19 | return g.NumberObject(0.0) 20 | } 21 | 22 | return g.NumberObject(1.0) 23 | } 24 | } 25 | 26 | func parseNumber(scope g.Scope, args []g.Object) g.Object { 27 | arguments := g.EvalArgs(scope, args) 28 | str := arguments[0].Value.Head 29 | num, err := strconv.ParseFloat(str[1:len(str) - 1], 64) 30 | if err != nil { return g.UndefinedObject() } 31 | 32 | return g.NumberObject(num) 33 | } 34 | 35 | var Exports = g.MapObject(map[string]g.Object{ 36 | "isString": g.BuiltinFunctionObject("isString", 37 | typeCheck(g.ObjectTypeLiteral, g.STNodeTypeStringLiteral)), 38 | "isNumber": g.BuiltinFunctionObject("isNumber", 39 | typeCheck(g.ObjectTypeLiteral, g.STNodeTypeNumberLiteral)), 40 | "isFunction": g.BuiltinFunctionObject("isFunction", 41 | typeCheck(g.ObjectTypeFunction, g.STNodeTypeIdentifier)), 42 | "isList": g.BuiltinFunctionObject("isList", 43 | typeCheck(g.ObjectTypeList, g.STNodeTypeIdentifier)), 44 | "isMap": g.BuiltinFunctionObject("isMap", 45 | typeCheck(g.ObjectTypeMap, g.STNodeTypeIdentifier)), 46 | 47 | "parseNumber": g.BuiltinFunctionObject("parseNumber", parseNumber), 48 | }) 49 | -------------------------------------------------------------------------------- /test-files/assert.golsp: -------------------------------------------------------------------------------- 1 | 2 | const assert [require "stdlib/assert.golsp"] 3 | 4 | const a 1 5 | assert a 6 | assert == a 1 7 | assert == a 2 8 | -------------------------------------------------------------------------------- /test-files/boolean.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [a x] [* x 2]] 3 | 4 | [def b 3] 5 | 6 | [def cond [== b [/ [a b] 2]]] 7 | 8 | [printf "%v\n" [== undefined 1]] 9 | [printf "%v\n" [>= x y]] 10 | 11 | [if cond 12 | [printf "true\n"]] 13 | 14 | [if [== "test" "test"] 15 | [printf "test defdefdef test\n"]] 16 | 17 | [if [!= "test" "test"] 18 | [printf "true expr\n"] 19 | [printf "false expr\n"]] 20 | -------------------------------------------------------------------------------- /test-files/const.golsp: -------------------------------------------------------------------------------- 1 | 2 | [const types [require "stdlib/types.golsp"]] 3 | [printf "%v %v\n" [types.isMap types] types] 4 | [def types "hello"] 5 | [printf "%v %v\n" [types.isMap types] types] 6 | [do 7 | [def types "quux"] 8 | [printf "%v %v\n" [types.isMap types] types] 9 | ] 10 | 11 | [[def [func x] [def types x]] "quux"] 12 | [printf "%v %v\n" [types.isMap types] types] 13 | -------------------------------------------------------------------------------- /test-files/do.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [a x] [/ x 2]] 3 | 4 | [def [c] 5 | [do 6 | [def b [a 7]] 7 | [printf "b: %v\n" b] 8 | [def [a 1] 2] 9 | [printf "%v\n" [a 3]] 10 | [printf "%v\n" [a 1]]]] 11 | 12 | [c] 13 | [printf "%v\n" [a 8]] 14 | [printf "%v\n" b] 15 | -------------------------------------------------------------------------------- /test-files/dot.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def types [require "stdlib/types.golsp"]] 3 | 4 | [printf "%v %v %v\n" [types.isString "hello"] [types.isNumber "test"] [types.isFunction types.isFunction]] 5 | 6 | [def map ( 7 | "hello": "world" 8 | "a": 1 9 | "b": 3 10 | "map": ( 11 | "chuchu": "dog" 12 | ) 13 | )] 14 | 15 | [printf map.map.chuchu] 16 | [printf "\n%v\n" [types.isMap map.map]] 17 | [printf "%v\n" 18 | ( 19 | "a": "b" 20 | "hello": "quux" 21 | "map": ( 22 | "foo": "bar" 23 | ) 24 | ).map.foo 25 | ] 26 | 27 | [def [makemap a b] ( a: b )] 28 | 29 | [printf "%v %v\n" [makemap "xyz" "cat"].xyz [makemap "quux" "baz"].jj] 30 | -------------------------------------------------------------------------------- /test-files/equals.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def a 1] 3 | [def b a] 4 | [def [c] a] 5 | [printf "a: %v b: %v c: %v\n" a b [c]] 6 | 7 | [def a 2] 8 | [printf "a: %v b: %v c: %v\n" a b [c]] 9 | -------------------------------------------------------------------------------- /test-files/fp.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [compose {} input] input] 3 | [def [compose { head tail... } input] 4 | [compose 5 | tail 6 | [head input] 7 | ] 8 | ] 9 | 10 | [def [double x] [* x 2]] 11 | 12 | [printf "%v\n" [compose { [lambda [x] [+ x 1]] double double [lambda [x] [+ x 1]] } 4]] 13 | 14 | [def [filter f {}] {}] 15 | [def [filter check { head tail... }] 16 | [if [check head] 17 | { head [filter check tail]... } 18 | [filter check tail] 19 | ] 20 | ] 21 | 22 | [def [map f {}] {}] 23 | [def [map f { head tail... }] 24 | { [f head] [map f tail]... }] 25 | 26 | [printf "%v\n" [filter [lambda [x] [> x 2]] { 3 4 2 1 4 5 2 6 2 }]] 27 | [printf "%v\n" [map [lambda [x] [* x x]] { 1 2 3 4 }]] 28 | 29 | [def [merge l1 {}] l1] 30 | [def [merge {} l2] l2] 31 | [def [merge { headl taill... } { headr tailr... }] 32 | [if [< headl headr] 33 | { headl [merge taill { headr tailr... }]... } 34 | { headr [merge { headl taill... } tailr]... } 35 | ] 36 | ] 37 | 38 | [def [len {}] 0] 39 | [def [len { _ tail... }] [+ 1 [len tail]]] 40 | 41 | [def [mergesort {}] {}] 42 | [def [mergesort { x }] { x }] 43 | [def [mergesort list] 44 | [do 45 | [def midpoint [/ [len list] 2]] 46 | [def left [list 0 midpoint]] 47 | [def right [list midpoint undefined]] 48 | [merge [mergesort left] [mergesort right]] 49 | ] 50 | ] 51 | 52 | [printf "%v\n" [merge { 1 2 3 4 } { 4 5 6 7 8 9 }]] 53 | 54 | [printf "%v\n" [mergesort { 1 12 3 23 41 5 4 32 12 17 20 75 23 }]] 55 | -------------------------------------------------------------------------------- /test-files/functions.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [incr n] [+ n 1]] 3 | [def [decr n] [+ n -1]] 4 | 5 | [def [twice f x] [f [f x]]] 6 | 7 | [def [functions 0] incr] 8 | [def [functions 1] decr] 9 | 10 | [printf "%v\n" [[functions 0] 1]] 11 | [printf "%v\n" [[functions 1] 1]] 12 | [printf "%v\n" [twice [functions 0] 1]] 13 | [printf "%v\n" [twice [def [f n] [+ n 2]] 3]] 14 | [printf "%v\n" [f 2]] 15 | -------------------------------------------------------------------------------- /test-files/go.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def x 1] 3 | 4 | [go 5 | [sleep 500] 6 | [def x 2] 7 | [printf "hello %v\n" x]] 8 | 9 | [sleep 1000] 10 | [printf "world %v\n" x] 11 | -------------------------------------------------------------------------------- /test-files/go2.golsp: -------------------------------------------------------------------------------- 1 | 2 | def result [go 3 | sleep 500 4 | "world" 5 | ] 6 | 7 | printf "%v " "hello" 8 | printf "%v\n" [result.wait] 9 | -------------------------------------------------------------------------------- /test-files/hello.golsp: -------------------------------------------------------------------------------- 1 | [printf "hello world\nstring argument: %v number argument: %v\n" "test" -3.5] 2 | -------------------------------------------------------------------------------- /test-files/lambda.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [compose {} input] input] 3 | 4 | [def [compose functions input] 5 | [compose 6 | [functions 0 -1] 7 | [[functions -1] input] 8 | ] 9 | ] 10 | 11 | [printf "%v\n" 12 | [compose 13 | { [lambda [x] [+ x 5]] [lambda [x] [* x 2]] } 14 | 7 15 | ] 16 | ] 17 | -------------------------------------------------------------------------------- /test-files/lists.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def f {+ - / +}] 3 | [def a {1 2 3 "a" "b" f}] 4 | 5 | [def [b] {{ [+ [a 1] 3] [+ [a 0] 2] }... 4}] 6 | 7 | [printf "%v\n" a] 8 | [printf "%v\n" [a -1]] 9 | [printf "%v\n" [b]] 10 | 11 | [printf "%v\n" [a -1 0 -1]] 12 | [printf "%v\n" [a 1 undefined]] 13 | -------------------------------------------------------------------------------- /test-files/math.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [incr n] [+ n 1]] 3 | [def [double x] [+ x x]] 4 | [def double2 double] 5 | [def double3 double2] 6 | 7 | [printf "1 + 2 = %v\n" [incr 2]] 8 | [printf "1 / 2 = %v\n" [/ 1 2]] 9 | [printf "3 * (3 / 4) = %v\n" [* [/ 3 4] 3]] 10 | [printf "2 * 3 = %v\n" [double 3]] 11 | [printf "1 + (2 * 4) = %v\n" [incr [double 4]]] 12 | [printf "double2: 2 * 6 = %v\n" [double2 6]] 13 | [printf "double3: 2 * \"hello\" = %v\n" [double3 "hello"]] 14 | 15 | [def [doge "chuchu"] "xyz"] 16 | [def [doge n] n] 17 | 18 | [printf "[doge \"chuchu\"]: %v\n" [doge "chuchu"]] 19 | [printf "[doge \"hello\"]: %v\n" [doge "hello"]] 20 | 21 | [def [not x] [% [+ x 1] 2]] 22 | [printf "%v\n" [not 1]] 23 | [printf "%v\n" [not 0]] 24 | -------------------------------------------------------------------------------- /test-files/oop.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [newPerson name age] 3 | [do 4 | [def [person "name"] name] 5 | [def [person "age"] age] 6 | [def [person "age+1"] [+ 1 [person "age"]]] 7 | person 8 | ] 9 | ] 10 | 11 | [def ajay [newPerson "Ajay" 18]] 12 | [printf "ajay.name: %v\n" [ajay "name"]] 13 | [printf "ajay.age: %v\n" [ajay "age"]] 14 | [printf "ajay.age+1: %v\n" [ajay "age+1"]] 15 | [def [ajay "age"] 19] 16 | [printf "ajay.age: %v\n" [ajay "age"]] 17 | [printf "ajay.age+1: %v\n" [ajay "age+1"]] 18 | -------------------------------------------------------------------------------- /test-files/os.golsp: -------------------------------------------------------------------------------- 1 | 2 | const os [require "stdlib/os.golsp"] 3 | const types [require "stdlib/types.golsp"] 4 | 5 | def rf [when 6 | [== [__args__ 0] "-"]: os.stdin 7 | 1: [os.open [__args__ 0]] 8 | ] 9 | 10 | def wf [when 11 | [__args__ 1]: [os.open [__args__ 1]] 12 | 1: os.stdout 13 | ] 14 | 15 | os.write wf [os.readAll rf] 16 | -------------------------------------------------------------------------------- /test-files/os2.golsp: -------------------------------------------------------------------------------- 1 | 2 | const os [require "stdlib/os.golsp"] 3 | const types [require "stdlib/types.golsp"] 4 | 5 | def fd [when 6 | [== [__args__ 0] "-"]: os.stdin 7 | 1: [os.open [__args__ 0]] 8 | ] 9 | 10 | printf "contents:\n\"\"\"\n%v\n\"\"\"\n" [os.readUntil fd "\n"] 11 | -------------------------------------------------------------------------------- /test-files/os3.golsp: -------------------------------------------------------------------------------- 1 | 2 | const os [require "stdlib/os.golsp"] 3 | 4 | printf "stat: %v\n" [os.stat [__args__ 0]] 5 | const fp [os.open [__args__ 0]] 6 | printf "contents: %v\n" [os.readAll fp] 7 | os.seek fp 0 8 | printf "contents again: %v\n" [os.readAll fp] 9 | -------------------------------------------------------------------------------- /test-files/os4.golsp: -------------------------------------------------------------------------------- 1 | 2 | const os [require "stdlib/os.golsp"] 3 | const assert [require "stdlib/assert.golsp"] 4 | 5 | printf "creating file './foo'... " 6 | def fp [os.create "./foo"] 7 | printf "done.\n" 8 | 9 | printf "writing to file... " 10 | os.write fp "hello" 11 | printf "done.\n" 12 | 13 | printf "creating directories ./bar/quux/baz... " 14 | assert os.mkdir "./bar/quux/baz" 15 | printf "done.\n" 16 | 17 | printf "remove? " 18 | def resp [os.readUntil os.stdin "\n"] 19 | 20 | if [== resp "y"] [do 21 | os.remove "./foo" 22 | os.removeAll "./bar" 23 | ] 24 | -------------------------------------------------------------------------------- /test-files/patterns.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def a "test"] 3 | 4 | [def [b "test"] "chuchu"] 5 | [def [b x] "xyz"] 6 | 7 | [printf "%v\n" [b "foo"]] 8 | [printf "%v\n" [b a]] 9 | [printf "%v\n" [b "test"]] 10 | 11 | [def [len {}] 0] 12 | 13 | [def [len list] 14 | [+ 1 15 | [len [list 0 -1]]]] 16 | 17 | [printf "%v\n" [len {1 2 3}]] 18 | [printf "%v\n" [len {1}]] 19 | 20 | [def [! 0] 1] 21 | [def [! n] [* n [! [- n 1]]]] 22 | 23 | [printf "%v\n" [! 6]] 24 | -------------------------------------------------------------------------------- /test-files/require/module/mod2/file.golsp: -------------------------------------------------------------------------------- 1 | 2 | [printf "dirname: %v filename: %v args: %v\n" __dirname__ __filename__ __args__] 3 | [sleep 1000] 4 | {1 2 3 4} 5 | -------------------------------------------------------------------------------- /test-files/require/module/module.golsp: -------------------------------------------------------------------------------- 1 | 2 | [require "./mod2/file.golsp"] 3 | -------------------------------------------------------------------------------- /test-files/require/req.golsp: -------------------------------------------------------------------------------- 1 | 2 | [printf "dirname: %v args: %v\n" __dirname__ __args__] 3 | [printf "%v\n" [require "./module/module.golsp"]] 4 | [printf "%v\n" [require "./module/mod2/file.golsp"]] 5 | [printf "dirname: %v args: %v\n" __dirname__ __args__] 6 | -------------------------------------------------------------------------------- /test-files/scope.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def x 2] 3 | [def [func] [printf "x: %v\n" x]] 4 | 5 | [def [closure a] 6 | [lambda [] 7 | [+ x a] 8 | ] 9 | ] 10 | 11 | [go 12 | [def x 4] 13 | [sleep 1000] 14 | [func] 15 | [printf "closure: %v\n" [[closure 1]]] 16 | ] 17 | 18 | [func] 19 | [printf "closure: %v\n" [[closure 1]]] 20 | [def x 3] 21 | [func] 22 | [printf "closure: %v\n" [[closure 1]]] 23 | -------------------------------------------------------------------------------- /test-files/spread.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def a { 1 2 3 }] 3 | 4 | [def b { 4 5 6 }] 5 | 6 | [def c { a... b... }] 7 | 8 | [def [d] { a... b... c... }] 9 | 10 | [def [q l] { * l... }] 11 | 12 | [def [reverse list] 13 | { [list -1 0 -1]... [list 0] }] 14 | 15 | [printf "%v\n" c] 16 | [printf "%v\n" [d]] 17 | [printf "%v\n" [reverse c]] 18 | [printf "%v\n" [{ + 1 2 }...]] 19 | [printf "%v\n" [{ reverse { 1 2 3 } }...]] 20 | 21 | [printf "%v\n" [{ + 1 2 3 }... 4]] 22 | [printf "%v\n" [[q { 4 5 6 }]...]] 23 | 24 | [printf "%v\n" 25 | [do 26 | [def l { "trsaasdfd" "sdfjsdk" "dfkj" }] 27 | l...]] 28 | 29 | [printf "%v\n" [{}...]] 30 | -------------------------------------------------------------------------------- /test-files/strings.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def sentence "-the-quick-brown-fox-jumps-over-the-lazy-dog------"] 3 | 4 | [def [merge ord l1 {}] l1] 5 | [def [merge ord {} l2] l2] 6 | [def [merge ord { headl taill... } { headr tailr... }] 7 | [if [ord headr headl] 8 | { headl 9 | [merge ord taill { headr tailr... }]... } 10 | { headr 11 | [merge ord { headl taill... } tailr]... } 12 | ] 13 | ] 14 | 15 | [def [len {}] 0] 16 | [def [len { _ tail... }] [+ 1 [len tail]]] 17 | 18 | [def [mergesort ord {}] {}] 19 | [def [mergesort ord { x }] { x }] 20 | [def [mergesort ord list] 21 | [do 22 | [def midpoint [/ [len list] 2]] 23 | [def left [list 0 midpoint]] 24 | [def right [list midpoint undefined]] 25 | [merge ord [mergesort ord left] [mergesort ord right]] 26 | ] 27 | ] 28 | 29 | [def [joinstr {}] ""] 30 | [def [joinstr { head tail... }] [sprintf "%v%v" head [joinstr tail]]] 31 | 32 | [printf "%v\n" [joinstr [mergesort > { sentence... }]]] 33 | 34 | [def [splitlist {} sep] {}] 35 | [def [splitlist { head tail... } sep] 36 | [do 37 | [def rest [splitlist tail sep]] 38 | [if [== head sep] 39 | { {} rest... } 40 | { { head [rest 0]... } [rest 1 undefined]... } 41 | ] 42 | ] 43 | ] 44 | 45 | [def [map f {}] {}] 46 | [def [map f { head tail... }] { [f head] [map f tail]... }] 47 | 48 | [printf "%v\n" [map joinstr [splitlist { sentence... } "-"]]] 49 | -------------------------------------------------------------------------------- /test-files/syntax2.golsp: -------------------------------------------------------------------------------- 1 | 2 | const types [require "stdlib/types.golsp"] 3 | 4 | const [typeof x] [when 5 | [types.isString x]: "string" 6 | [types.isNumber x]: "number" 7 | [types.isFunction x]: "function" 8 | [types.isMap x]: "map" 9 | [types.isList x]: "list" 10 | 1: undefined] 11 | 12 | printf "%v\n" [typeof printf] 13 | [printf 14 | "%v\n" 15 | typeof "hello" 16 | ] 17 | -------------------------------------------------------------------------------- /test-files/types.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def types [require "stdlib/types.golsp"]] 3 | 4 | [printf "%v %v %v\n" [[types "isString"] "hello"] [[types "isString"] 2.0] [[types "isNumber"] 3.1]] 5 | [printf "%v %v %v\n" [[types "isMap"] types] [[types "isList"] [types types...]] [[types "isFunction"] [types "isFunction"]]] 6 | [printf "%v %v\n" [[types "isNumber"] "test"] [[types "isList"] types]] 7 | 8 | printf "%v\n" [types.parseNumber "12"] 9 | -------------------------------------------------------------------------------- /test-files/util.golsp: -------------------------------------------------------------------------------- 1 | 2 | const _ [require "stdlib/tools.golsp"] 3 | 4 | const list [_.range 11] 5 | 6 | printf "%v\n" [_.range 5 15 2] 7 | printf "%v\n" [_.range 20 1 -3] 8 | 9 | printf "%v\n" [_.filter [lambda [x] [== [% x 2] 0]] list] 10 | printf "%v\n" [_.map [lambda [x] [* x x]] list] 11 | 12 | def [double x] [* x 2] 13 | def [square x] [* x x] 14 | def [incr x] [+ x 1] 15 | 16 | printf "%v\n" [_.compose 12 { square double incr }] 17 | printf "%v\n" [_.compose 12 square double incr] 18 | printf "%v\n" [_.compose 19 | list 20 | { _.filter [lambda [x] [== [% x 2] 0]] } 21 | { _.map square } 22 | ] 23 | -------------------------------------------------------------------------------- /test-files/when.golsp: -------------------------------------------------------------------------------- 1 | 2 | [const types [require "stdlib/types.golsp"]] 3 | 4 | [const [typeof x] 5 | [when 6 | [types.isString x]: "string" 7 | [types.isNumber x]: "number" 8 | [types.isMap x]: "map" 9 | [types.isList x]: "list" 10 | [types.isFunction x]: "function" 11 | 1: undefined 12 | ] 13 | ] 14 | 15 | [printf "typeof %v: %v\n" 3.1 [typeof 3.1]] 16 | [printf "typeof %v: %v\n" "hello" [typeof "hello"]] 17 | [printf "typeof %v: %v\n" { 1 2 3 "a" } [typeof { 1 2 3 "a" }]] 18 | [printf "typeof %v: %v\n" ( "a": 1 2: "b" ) [typeof ( "a": 1 2: "b" )]] 19 | [printf "typeof %v: %v\n" printf [typeof printf]] 20 | [printf "typeof %v: %v\n" undefined [typeof undefined]] 21 | -------------------------------------------------------------------------------- /test-files/zip.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def mymap ( 3 | "a": 1 4 | "b": 2 5 | "c": 3 6 | 4: "d" 7 | )] 8 | 9 | [printf "%v\n" mymap] 10 | 11 | [def keys { 5 6 7 8 }] 12 | [def values "abc"] 13 | [def zipmap ( 14 | keys... : values... 15 | 8: "f" 16 | )] 17 | 18 | [printf "%v\n" zipmap] 19 | 20 | [printf "\n[zipmap 7]: %v\n" [zipmap 7]] 21 | [printf "[mymap \"b\"]: %v\n" [mymap "b"]] 22 | [printf "[zipmap 9]: %v\n" [zipmap 9]] 23 | [printf "zipmap keys: %v\n" { zipmap... }] 24 | [printf "zipmap values: %v\n" [zipmap zipmap...]] 25 | 26 | [def nestedmap ( 27 | "foo": "bar" 28 | "quux": 1 29 | "baz": ( 30 | "llvm": {"clang" "lldb" "darwin"} 31 | ) 32 | )] 33 | 34 | [printf "\nnestedmap: %v\n" nestedmap] 35 | [printf "nestedmap values: %v\n" [nestedmap nestedmap...]] 36 | [printf "nestedmap.baz.llvm[1:undefined]: %v\n" [[[nestedmap "baz"] "llvm"] 1 undefined]] 37 | 38 | [def zipmap2 ( 39 | zipmap... : [zipmap zipmap...]... 40 | 7: "g" 41 | )] 42 | 43 | [printf "\nzipmap2: %v\n" zipmap2] 44 | -------------------------------------------------------------------------------- /test-files/zip2.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def map ( 3 | "hello" : "world" 4 | "test" : 1 5 | "a" : "b" 6 | 4 : "1" 7 | 6 : 8 8 | )] 9 | 10 | [def [func] 11 | [go 12 | [sleep 500] 13 | [def map ( 14 | map... : [map map...]... 15 | 4 : 17 16 | )] 17 | [printf "%v\n" map] 18 | ] 19 | ] 20 | 21 | [func] 22 | [sleep 1000] 23 | [printf "%v\n" map] 24 | -------------------------------------------------------------------------------- /test-files/zip3.golsp: -------------------------------------------------------------------------------- 1 | 2 | [def [myfunc ( "hello" : hello keys... )] 3 | [sprintf "hello: %v %v %v" hello hello keys] 4 | ] 5 | 6 | [def [myfunc ( keys... )] [sprintf "keys: %v\n" keys]] 7 | 8 | [printf "%v\n" [myfunc ( "a": 1 "b": 2 "hello": 3 )]] 9 | [printf "%v\n" [myfunc ( "hello": "world" 1: 4 5: 7 )]] 10 | [printf "%v\n" [myfunc ( "a": 1 "b": 2 "c": "d" )]] 11 | 12 | [def [values ( keys... )]] 13 | 14 | [def [f2 ( "chuchu" : chuchuval keys... : values... )] 15 | [printf "chuchu: %v keys: %v values: %v\n" chuchuval keys values] 16 | ] 17 | 18 | [def [f2 ( keys... : values... )] 19 | [printf "keys: %v values: %v\n" keys values] 20 | ] 21 | 22 | [f2 ( "chuchu": 1 "xyz": 2 "geoff": 3 )] 23 | [f2 ( "maxk3": 1 "xyz": 2 "geoff": 3 )] 24 | --------------------------------------------------------------------------------