├── .gitignore ├── license.txt ├── readme.markdown ├── spec.markdown ├── src ├── core.nim ├── interpreter.nim ├── lexer.nim ├── lib │ ├── math.nael │ └── pythag.nael ├── main.nim ├── parser.nim ├── system.nael └── tests │ ├── funcargs.nael │ └── funcargs2.nael └── todo.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache 2 | parser 3 | lexer 4 | interpreter 5 | variables 6 | stack 7 | unity 8 | core 9 | main 10 | IRC 11 | IRC.nim 12 | *.exe 13 | Build.Win32.bat -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Dominik Picheta 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL DOMINIK PICHETA BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | nael 2 | ==== 3 | --- 4 | An experimental language, based on the stack-oriented programming language by the name of Factor. 5 | 6 | nael is licensed under the BSD License, see license.txt for more information. -------------------------------------------------------------------------------- /spec.markdown: -------------------------------------------------------------------------------- 1 | nael specification v0.1 2 | ======================= 3 | --- 4 | The algorithm for evaluating nael expressions, is pretty straightforward, and very similar to the postfix algorithm described [here](http://en.wikipedia.org/wiki/Reverse_Polish_notation#Postfix_algorithm "here") 5 | 6 | * While there are input tokens left 7 | * Read the next token from input 8 | * If the token is a value 9 | * Push it onto the stack 10 | * Otherwise, the token is a function. 11 | * It is known [a priori](http://www.google.com/search?client=ubuntu&channel=fs&q=define%3A+a+priori&ie=utf-8&oe=utf-8) that the function takes **n** arguments. 12 | * If there are fewer than **n** values on the stack 13 | * **(ERROR):** Not enough values on the stack. 14 | * Else, pop the top **n** values from the stack. 15 | * Evaluate the operator, with the values as arguments. 16 | * Push the returned results, if any, back onto the stack. 17 | 18 | ## strings ## 19 | `>> "string"` 20 | 21 | `stack → ["string"]` 22 | 23 | ## integers ## 24 | `>> 5` 25 | 26 | `stack → [5]` 27 | 28 | ## Arithmetic Expressions ## 29 | First two 5's are pushed on the stack, then `+` is called. 30 | 31 | `>> 5 5 +` 32 | 33 | `stack → [10]` 34 | 35 | `-`, `*` and `/` work as you would expect. A good thing to remember is that "`10 2 /`" is "`10 / 2`" in [Infix Notation](http://en.wikipedia.org/wiki/Infix_notation "Infix Notation") 36 | 37 | ## Lists ## 38 | Lists can contain any type. 39 | 40 | `>> ["string", 56]` 41 | 42 | `stack → [["string", 56]]` 43 | 44 | 45 | ## Statements ## 46 | Push a string, and write it to stdout. That's how you call statements by the way. 47 | 48 | `>> "Hello, world!" print` 49 | 50 | `Hello, world!` 51 | 52 | ### ! ### 53 | Negates a boolean. 54 | 55 | `>> false !` 56 | 57 | `stack → [true]` 58 | 59 | ### if ### 60 | Executes a quotation based on the value on the stack. 61 | 62 | `>> (true) ("The value is true" print) ("The value is false" print) if` 63 | 64 | `The value is true` 65 | 66 | ### while ### 67 | Executes a quotation until the *cond* quotation pushes false on the stack 68 | 69 | `>> (true) ("Infinite loop" print) while` 70 | 71 | `Infinite loop` * forever 72 | 73 | ### each ### 74 | Iterates over a list 75 | 76 | `>> [1, 2, 3, 4] (print) each` 77 | 78 | `1` 79 | 80 | `2` 81 | 82 | `3` 83 | 84 | `4` 85 | 86 | ## Modules ## 87 | You can import modules using the `import` keyword. 88 | 89 | `"math" import` 90 | 91 | You can then access the modules functions with, `module.function` 92 | 93 | You can also use `include` instead of `import` which will import all the functions, which means you will be able to just do `function` 94 | 95 | ## Variables ## 96 | Before you can set a variables value you need to declare it. 97 | 98 | `>> x let` 99 | 100 | You can then set it to whatever value you want. nael is a dynamically typed language. 101 | 102 | `>> x 123 =` 103 | 104 | `>> x get print` 105 | 106 | `123` 107 | 108 | **Note:** Whenever you declare a variable it's default value is nil 109 | 110 | ## Anonymous functions ## 111 | Anonymous functions are code snippets which haven't been executed yet. 112 | Functions have their own scope, while anonymous functions share scope with the scope of the function that it was executed in. 113 | 114 | Anonymous functions are declared with `(` and `)`, code in between the parenthesis will become an anonymous function and will get pushed to the stack. 115 | 116 | `>> ("foo" print)` 117 | 118 | `stack → [("foo" print)]` 119 | 120 | You can then execute this code, by using the built in keyword, `call` 121 | 122 | `>> call` 123 | 124 | `foo` 125 | 126 | ## Functions ## 127 | Declaring functions is easy. And calling them is easy too. 128 | 129 | `>> func foo [arg] (arg print, arg " again" + print);` 130 | 131 | `>> "hi" foo` 132 | 133 | `hi` 134 | 135 | `hi again` 136 | 137 | You can also have multi-line functions. 138 | 139 | func foo [arg] 140 | ( 141 | arg print 142 | arg " again" + print 143 | ); 144 | 145 | A thing to remember about functions is that they do not share scope. Everything you declare with the `let` keyword, inside a function will be deallocated when the function returns. However you can declare `global variables` with the `def` keyword. 146 | 147 | ## Tuples ## 148 | 149 | `>> bar [field, field1] tuple` 150 | 151 | `>> bar new, instance swap =` 152 | 153 | `>> instance.field 10 =, instance.field1 2 =` 154 | 155 | `>> instance.field instance.field1 /` 156 | 157 | `5` 158 | 159 | # nael 0.2 - ideas 160 | 161 | ## Functions 162 | The interpreter could infer the stack effect of functions. 163 | 164 | `func a [] (+);` 165 | 166 | Would be inferred as, `String String -- String` 167 | 168 | The programmer could also specify the stack effect of functions. Like in haskell. 169 | 170 | `a :: String String -- String` 171 | 172 | Perhaps a different function declaration syntax would be better. 173 | 174 | Getting rid of ( ) might be a good idea, instead for a non rpn syntax. 175 | 176 | `a := +` 177 | 178 | `a arg = arg get +` 179 | -------------------------------------------------------------------------------- /src/core.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2010 (C) Dominik Picheta - Read license.txt for the license. 2 | # 31 May 2010 3 | 4 | # core - Implements commands like 'print' 5 | 6 | import random 7 | 8 | proc getModule(name: string, gvars: var PType): seq[PType] 9 | proc loadModules(modules: seq[string], vars, gvars: var PType) 10 | proc callFunc*(dataStack: var TStack, vars, gvars: var PType, cmnd: string, 11 | tVar: PType, module: var seq[PType]) 12 | 13 | proc invalidTypeErr(got, expected, function: string): ref RuntimeError = 14 | return newException(RuntimeError, errorLine() & "Error: Invalid types, $1 expects $2, got $3" % [function, expected, got]) 15 | 16 | proc command*(cmnd: string, dataStack: var TStack, vars, gvars: var PType) = 17 | case cmnd 18 | of "print": 19 | var first = dataStack.pop() 20 | echo(toString(first)) 21 | 22 | of "call": 23 | var first = dataStack.pop() 24 | if first.kind == ntQuot: 25 | interpretQuotation(first, dataStack, vars, gvars) 26 | else: 27 | raise invalidTypeErr($first.kind, "quot", "call") 28 | 29 | of "ccall": 30 | # "header" "function" [args] ccall 31 | var args = dataStack.pop() 32 | var function = dataStack.pop() 33 | var header = dataStack.pop() 34 | if function.kind == ntString and header.kind == ntString and args.kind == ntList: 35 | dataStack.push(newString("Sorry this doesn't work yet :(")) 36 | when false: 37 | var lib = LoadLib(header.value) 38 | var funcPtr = checkedSymAddr(lib, function.value) 39 | var fun = cast[proc(x: float): float](funcPtr) 40 | var arg = 4.5 41 | echo(cast[int](addr(arg))) 42 | var val = fun(arg) 43 | echo(val) 44 | else: 45 | raise invalidTypeErr($args.kind & $function.kind & $header.kind, "list, string and string", "ccall") 46 | 47 | of "import": 48 | var first = dataStack.pop() 49 | if first.kind == ntString: 50 | loadModules(@[first.value], vars, gvars) 51 | elif first.kind == ntList: 52 | var modList: seq[string] = @[] 53 | for i in items(first.lValue): 54 | if i.kind == ntString: 55 | modList.add(i.value) 56 | else: 57 | raise invalidTypeErr($i.kind, "string", "call") 58 | 59 | loadModules(modList, vars, gvars) 60 | else: 61 | raise invalidTypeErr($first.kind, "string or list", "call") 62 | 63 | of "include": 64 | var first = dataStack.pop() 65 | if first.kind == ntString: 66 | includeModules(@[first.value], dataStack, vars, gvars) 67 | elif first.kind == ntList: 68 | var modList: seq[string] = @[] 69 | for i in items(first.lValue): 70 | if i.kind == ntString: 71 | modList.add(i.value) 72 | else: 73 | raise invalidTypeErr($i.kind, "string", "call") 74 | 75 | includeModules(modList, dataStack, vars, gvars) 76 | else: 77 | raise invalidTypeErr($first.kind, "string or list", "call") 78 | 79 | of "+", "-", "*", "/", "%": 80 | var first = datastack.pop() 81 | var second = datastack.pop() 82 | 83 | if cmnd == "+": 84 | if first.kind == ntInt and second.kind == ntInt: 85 | dataStack.push(newInt(second.iValue + first.iValue)) 86 | elif first.kind == ntString and second.kind == ntString: 87 | dataStack.push(newString(second.value & first.value)) 88 | elif first.kind == ntFloat and second.kind == ntFloat: 89 | dataStack.push(newFloat(second.fvalue + first.fvalue)) 90 | elif first.kind == ntInt and second.kind == ntFloat: 91 | dataStack.push(newFloat(second.fvalue + float(first.ivalue))) 92 | elif first.kind == ntFloat and second.kind == ntInt: 93 | dataStack.push(newFloat(float(second.ivalue) + first.fvalue)) 94 | else: 95 | raise invalidTypeErr($first.kind & " and " & 96 | $second.kind, "[string, string], [int, int], [float, float] or [float, int]", "+") 97 | 98 | elif cmnd == "-": 99 | if first.kind == ntInt and second.kind == ntInt: 100 | dataStack.push(newInt(second.iValue - first.iValue)) 101 | elif first.kind == ntFloat and second.kind == ntFloat: 102 | dataStack.push(newFloat(second.fvalue - first.fvalue)) 103 | else: 104 | raise invalidTypeErr($first.kind & " and " & 105 | $second.kind, "int, int or float, float", "-") 106 | 107 | elif cmnd == "*": 108 | if first.kind == ntInt and second.kind == ntInt: 109 | dataStack.push(newInt(second.iValue * first.iValue)) 110 | elif first.kind == ntFloat and second.kind == ntFloat: 111 | dataStack.push(newFloat(second.fvalue * first.fvalue)) 112 | else: 113 | raise invalidTypeErr($first.kind & " and " & 114 | $second.kind, "int, int or float, float", "*") 115 | 116 | elif cmnd == "/": 117 | if first.kind == ntInt and second.kind == ntInt: 118 | if first.iValue == 0 or second.iValue == 0: 119 | raise newException(RuntimeError, errorLine() & 120 | "Error: Division by zero") 121 | 122 | if second.iValue %% first.iValue == 1: 123 | dataStack.push(newFraction(second.iValue, first.iValue)) 124 | else: 125 | dataStack.push(newInt(second.iValue div first.iValue)) 126 | 127 | elif first.kind == ntFloat and second.kind == ntFloat: 128 | dataStack.push(newFloat(second.fvalue / first.fvalue)) 129 | else: 130 | raise invalidTypeErr($first.kind & " and " & 131 | $second.kind, "int, int or float, float", "/") 132 | 133 | elif cmnd == "%": 134 | if first.kind == ntInt and second.kind == ntInt: 135 | dataStack.push(newInt(second.iValue %% first.iValue)) 136 | else: 137 | raise invalidTypeErr($first.kind & " and " & 138 | $second.kind, "int, int", "%") 139 | 140 | of "!": 141 | # Negate a boolean 142 | var first = dataStack.pop() 143 | if first.kind == ntBool: 144 | dataStack.push(newBool(not first.bValue)) 145 | else: 146 | raise invalidTypeErr($first.kind, "bool", "!") 147 | 148 | of "if": 149 | # Depending on a boolean on the stack, executes a particular quotation 150 | # (cond) (then) (else) if 151 | var theElse = datastack.pop() 152 | var then = datastack.pop() 153 | var cond = datastack.pop() 154 | 155 | if cond.kind == ntQuot and theElse.kind == ntQuot and then.kind == ntQuot: 156 | interpretQuotation(cond, dataStack, vars, gvars) 157 | var boolean = dataStack.pop() 158 | if boolean.kind != ntBool: 159 | raise invalidTypeErr($boolean.kind, "bool", "if") 160 | 161 | if boolean.bValue: 162 | interpretQuotation(then, dataStack, vars, gvars) 163 | else: 164 | interpretQuotation(theElse, dataStack, vars, gvars) 165 | else: 166 | raise invalidTypeErr($cond.kind & ", " & $theElse.kind & " and " & $then.kind, 167 | "quot, quot, quot", "if") 168 | 169 | of "while": 170 | # Loop until cond becomes false 171 | # (cond) (do) while 172 | var doAction = dataStack.pop() 173 | var cond = dataStack.pop() 174 | 175 | if doAction.kind == ntQuot and cond.kind == ntQuot: 176 | interpretQuotation(cond, dataStack, vars, gvars) 177 | var boolean = dataStack.pop() 178 | if boolean.kind != ntBool: 179 | raise invalidTypeErr($boolean.kind, "bool", "while") 180 | 181 | while boolean.bValue: 182 | interpretQuotation(doAction, dataStack, vars, gvars) 183 | 184 | interpretQuotation(cond, dataStack, vars, gvars) 185 | boolean = dataStack.pop() 186 | else: 187 | raise invalidTypeErr($doAction.kind & " and " & $cond.kind, "quot and quot", "while") 188 | 189 | of "==": 190 | var first = dataStack.pop() 191 | var second = dataStack.pop() 192 | 193 | dataStack.push(newBool(isEqual(first, second))) 194 | 195 | of ">": 196 | var first = dataStack.pop() 197 | var second = dataStack.pop() 198 | 199 | if first.kind == ntInt and second.kind == ntInt: 200 | dataStack.push(newBool(second.iValue > first.iValue)) 201 | elif first.kind == ntFloat and second.kind == ntFloat: 202 | dataStack.push(newBool(second.fValue > first.fValue)) 203 | else: 204 | raise invalidTypeErr($first.kind & " and " & $second.kind, "int, int or float, float", ">") 205 | 206 | of "<": 207 | var first = dataStack.pop() 208 | var second = dataStack.pop() 209 | 210 | if first.kind == ntInt and second.kind == ntInt: 211 | dataStack.push(newBool(second.iValue < first.iValue)) 212 | elif first.kind == ntFloat and second.kind == ntFloat: 213 | dataStack.push(newBool(second.fValue < first.fValue)) 214 | else: 215 | raise invalidTypeErr($first.kind & " and " & $second.kind, "int, int or float, float", "<") 216 | 217 | of "and": 218 | var first = dataStack.pop() 219 | var second = dataStack.pop() 220 | if first.kind == ntBool and second.kind == ntBool: 221 | dataStack.push(newBool(second.bValue and first.bValue)) 222 | else: 223 | raise invalidTypeErr($first.kind & " and " & $second.kind, "bool and bool", "and") 224 | 225 | of "or": 226 | var first = dataStack.pop() 227 | var second = dataStack.pop() 228 | if first.kind == ntBool and second.kind == ntBool: 229 | dataStack.push(newBool(second.bValue or first.bValue)) 230 | else: 231 | raise invalidTypeErr($first.kind & " and " & $second.kind, "bool and bool", "or") 232 | 233 | of "=": 234 | var first = dataStack.pop() 235 | var second = dataStack.pop() 236 | 237 | if second.kind == ntVar: 238 | if second.loc == 1: 239 | gvars.setVar(second.vvalue, first) 240 | elif second.loc == 0: 241 | vars.setVar(second.vvalue, first) 242 | elif second.loc == 2: 243 | vars.setVarField(second.vvalue, first) 244 | elif second.loc == 3: 245 | gvars.setVarField(second.vvalue, first) 246 | else: 247 | raise invalidTypeErr($second.kind, "var", "=") 248 | 249 | of "get": 250 | var first = dataStack.pop() 251 | if first.kind == ntVar: 252 | discard """ 253 | var tVar: PType 254 | 255 | if first.loc == 0: 256 | tVar = vars.getVar(first.vvalue) 257 | elif first.loc == 1: 258 | tVar = gvars.getVar(first.vvalue) 259 | elif first.loc == 2: 260 | tVar = vars.getVarField(first.vvalue) 261 | elif first.loc == 3: 262 | tVar = gvars.getVarField(first.vvalue) 263 | else: 264 | raise newException(RuntimeError, errorLine() & 265 | "Error: Variable's location is incorrect, got $1" % [$first.loc]) 266 | 267 | if tVar == nil: 268 | raise newException(RuntimeError, errorLine() & 269 | "Error: $1 is not declared." % [first.vvalue])""" 270 | 271 | # unreference the value, so that getting a list from a variable and appending 272 | # to it, doesn't make changes to the variable 273 | 274 | dataStack.push(copyVar(first.val)) 275 | 276 | else: 277 | raise invalidTypeErr($first.kind, "var", "get") 278 | 279 | of "del": 280 | discard dataStack.pop() 281 | 282 | of "delvar": 283 | var v = dataStack.pop() 284 | if v.kind == ntVar: 285 | if v.loc == 0: 286 | if vars.getVar(v.vvalue) != nil: 287 | vars.remVar(v.vvalue) 288 | elif v.loc == 1: 289 | if gvars.getVar(v.vvalue) != nil: 290 | gvars.remVar(v.vvalue) 291 | else: 292 | raise newException(RuntimeError, errorLine() & 293 | "Error: Cannot remove field variables, loc = $1" % [$v.loc]) 294 | 295 | of "__stack__": 296 | dataStack.push(newList(dataStack.stack)) 297 | 298 | of "swap": 299 | # [1, 2] -> [2, 1] 300 | var first = dataStack.pop() 301 | var second = dataStack.pop() 302 | dataStack.push(first) 303 | dataStack.push(second) 304 | 305 | # List manipulators 306 | of "nth": 307 | # [list] 5 nth 308 | # Gets the 5th item of list 309 | var index = dataStack.pop() 310 | var list = dataStack.pop() 311 | if index.kind == ntInt and list.kind == ntlist: 312 | # WTF, something is really wrong with exceptions 313 | try: 314 | dataStack.push(list.lValue[int(index.iValue)]) 315 | except: 316 | raise newException(RuntimeError, "Error: $1" % [getCurrentExceptionMsg()]) 317 | else: 318 | raise invalidTypeErr($index.kind & " and " & $list.kind, "int and list", "nth") 319 | 320 | of "len": 321 | # Pushes the length of a list or string 322 | var list = dataStack.pop() 323 | if list.kind == ntList: 324 | dataStack.push(newInt(list.lValue.len())) 325 | elif list.kind == ntString: 326 | dataStack.push(newInt(list.value.len())) 327 | else: 328 | raise invalidTypeErr($list.kind, "list or string", "len") 329 | 330 | of "append": 331 | # [list] 5 append 332 | # Appends a value to a list 333 | var value = dataStack.pop() 334 | var list = dataStack.pop() 335 | if list.kind == ntList: 336 | list.lValue.add(value) 337 | dataStack.push(list) 338 | else: 339 | raise invalidTypeErr($list.kind, "list", "append") 340 | 341 | # Math 342 | of "sqrt": 343 | var first = dataStack.pop() 344 | if first.kind == ntFloat: 345 | dataStack.push(newFloat(sqrt(first.fValue))) 346 | else: 347 | raise invalidTypeErr($first.kind, "float", "sqrt") 348 | 349 | of "pow": 350 | var first = dataStack.pop() 351 | var second = dataStack.pop() 352 | if first.kind == ntFloat and second.kind == ntFloat: 353 | dataStack.push(newFloat(pow(second.fValue, first.fValue))) 354 | else: 355 | raise invalidTypeErr($first.kind & " and " & $second.kind, "float and float", "pow") 356 | of "rand": 357 | var first = dataStack.pop() 358 | if first.kind == ntInt: 359 | randomize() 360 | dataStack.push(newInt(rand(int(first.iValue)))) 361 | else: 362 | raise invalidTypeErr($first.kind, "int", "rand") 363 | of "round": 364 | var first = dataStack.pop() 365 | if first.kind == ntFloat: 366 | dataStack.push(newInt(round(first.fValue).int64)) 367 | else: 368 | raise invalidTypeErr($first.kind, "float", "round") 369 | 370 | # Type conversion 371 | of "type": 372 | var first = dataStack.pop() 373 | dataStack.push(newString($first.kind)) 374 | 375 | of "int>float": 376 | var first = dataStack.pop() 377 | if first.kind == ntInt: 378 | dataStack.push(newFloat(float(first.iValue))) 379 | else: 380 | raise invalidTypeErr($first.kind, "int", "int>float") 381 | 382 | of "float>int": 383 | var first = dataStack.pop() 384 | if first.kind == ntFloat: 385 | dataStack.push(newInt(int(first.fValue))) 386 | else: 387 | raise invalidTypeErr($first.kind, "float", "float>int") 388 | 389 | of "string>int": 390 | var first = dataStack.pop() 391 | if first.kind == ntString: 392 | dataStack.push(newInt(first.value.parseInt())) 393 | else: 394 | raise invalidTypeErr($first.kind, "string", "string>int") 395 | 396 | # Exception handling 397 | of "try": 398 | # (try quot) (excpt quot) try 399 | var first = dataStack.pop() 400 | var second = dataStack.pop() 401 | if first.kind == ntQuot and second.kind == ntQuot: 402 | try: 403 | interpretQuotation(second, dataStack, vars, gvars) 404 | except: 405 | dataStack.push(newString(getCurrentExceptionMsg())) 406 | interpretQuotation(first, dataStack, vars, gvars) 407 | else: 408 | raise invalidTypeErr($first.kind & " and " & $second.kind, "quot and quot", "try") 409 | 410 | of "new": 411 | var first = dataStack.pop() 412 | if first.kind == ntVar: 413 | var tVar: PType 414 | 415 | if first.loc == 0: 416 | tVar = vars.getVar(first.vvalue) 417 | elif first.loc == 1: 418 | tVar = gvars.getVar(first.vvalue) 419 | elif first.loc == 2: 420 | tVar = vars.getVarField(first.vvalue) 421 | elif first.loc == 3: 422 | tVar = gvars.getVarField(first.vvalue) 423 | else: 424 | raise newException(RuntimeError, errorLine() & 425 | "Error: Variable's location is incorrect, got $1" % [$first.loc]) 426 | 427 | if tVar == nil: 428 | raise newException(RuntimeError, errorLine() & 429 | "Error: $1 is not declared." % [first.vvalue]) 430 | 431 | if tVar.kind == ntType: 432 | var fields: TDict = @[] 433 | for i in 0 .. len(tVar.fields)-1: 434 | fields.add((tVar.fields[i], newNil())) 435 | dataStack.push(newObject(tVar, fields)) 436 | 437 | else: 438 | # Variables and Functions 439 | var tVar: PType 440 | var varLoc: int = -1 # 0 for local, 1 for global 441 | var module: seq[PType] 442 | 443 | if not ("." in cmnd): 444 | tVar = vars.getVar(cmnd) 445 | varLoc = 0 446 | if tVar == nil: 447 | tVar = gvars.getVar(cmnd) 448 | varLoc = 1 449 | else: 450 | # cmnd contains a dot 451 | var s = cmnd.split('.') 452 | 453 | # Check if there is a module, with function by the name of whatever s[1] is 454 | module = getModule(s[0], gvars) # [name, {locals}, {globals}] 455 | if module == nil: 456 | tVar = nil 457 | else: 458 | tVar = module[2].getVar(s[1]) 459 | varLoc = 1 460 | 461 | # FIXME: Perhaps it shouldn't be a dot syntax 462 | # If i were to use a different method it would simpler i think. 463 | 464 | # if the module doesn't exist, then try to see if there is a object, with 465 | # a field equal to after 466 | if module == nil: 467 | tVar = vars.getVarField(cmnd) 468 | varLoc = 2 #i.e field, local 469 | if tVar == nil: 470 | tVar = gvars.getVarField(cmnd) 471 | varLoc = 3 #i.e field global 472 | 473 | if tVar == nil: 474 | raise newException(RuntimeError, errorLine() & "Error: $1 is not declared." % [cmnd]) 475 | 476 | if tVar.kind != ntFunc: 477 | dataStack.push(newVar(cmnd, varLoc, tVar)) 478 | else: 479 | callFunc(dataStack, vars, gvars, cmnd, tVar, module) 480 | 481 | proc interpretQuotation*(quot: PType, dataStack: var TStack, vars, gvars: var PType) = 482 | if quot.kind != ntQuot: 483 | raise newException(ValueError, errorLine() & 484 | "Error: Argument given is not a quotation") 485 | 486 | for item in items(quot.lvalue): 487 | case item.kind 488 | of ntInt, ntFloat, ntFraction, ntString, ntBool, ntList, ntQuot, ntDict, ntNil, ntFunc, ntVar, ntType, ntObject: 489 | dataStack.push(item) 490 | of ntCmnd: 491 | command(item.value, dataStack, vars, gvars) 492 | of ntAstNode: 493 | case item.node.kind: 494 | of nnkVarDeclar: 495 | if gvars.getVar(item.node.value) != nil: 496 | raise newException(RuntimeError, errorLine() & 497 | "Error: $1 is already declared as a global variable" % [item.node.value]) 498 | else: 499 | vars.declVar(item.node.value) 500 | of nnkFunc: 501 | if gvars.getVarIndex(item.node.fname) == -1: 502 | gvars.declVar(item.node.fName) 503 | gvars.setVar(item.node.fName, newFunc(item.node)) 504 | of nnkTuple: 505 | if gvars.getVarIndex(item.node.tName) == -1: 506 | gvars.declVar(item.node.tName) 507 | gvars.setVar(item.node.tName, newType(item.node)) 508 | 509 | else: 510 | raise newException(RuntimeError, errorLine() & 511 | "Error: Unexpected AstNode in quotation, " & $item.node.kind) 512 | 513 | proc callFunc*(dataStack: var TStack, vars, gvars: var PType, cmnd: string, 514 | tVar: PType, module: var seq[PType]) = 515 | # Function call - Functions don't share scope, but they share the stack. 516 | 517 | var localVars = newVariables() # This functions local variables 518 | var globalVars = newVariables() # This functions global variables 519 | # Copy the current gvars to this functions globalVars 520 | if module == nil: 521 | globalVars.dValue = gvars.dValue 522 | else: 523 | globalVars.dValue = module[2].dValue 524 | 525 | # Add the arguments in reverse, so that the 'nael rule' applies 526 | # 5 6 foo -> foo(5,6) 527 | # I have to add the args to globals, so that functions called from this function 528 | # that have a quotation passed to them(with one of the var args..) works 529 | # Look at tests/funcargs.nael for more info 530 | 531 | # FIXME: These should REALLY, not be added to global vars. I need to 532 | # find a solution to the whole args/scope thing. Remember that 533 | # quotations called in functions need to have the scope of the function. 534 | 535 | var tempArgNames: seq[string] = @[] 536 | for i in countdown(tVar.args.len()-1, 0): 537 | var arg = tVar.args[i] 538 | if arg.kind == ntCmnd: 539 | #try: 540 | var first = dataStack.pop() 541 | # If it's already declared just overwrite it. 542 | if globalVars.getVar(arg.value) == nil: 543 | globalVars.declVar(arg.value) 544 | globalVars.setVar(arg.value, first) 545 | 546 | tempArgNames.add(arg.value) 547 | 548 | #except EOverflow: 549 | # # TODO: Check if this works, After araq fixes the exception bug 550 | # raise newException(RuntimeError, 551 | # "Error: $1 expects $2 args, got $3" % 552 | # [cmnd, $(tVar.args.len()-1), $(i)]) 553 | 554 | else: 555 | raise newException(RuntimeError, errorLine() & "Error: " & cmnd & 556 | " contains unexpected args, of kind " & $arg.kind) 557 | 558 | 559 | interpretQuotation(tVar.quot, dataStack, localVars, globalVars) 560 | 561 | # TODO: If a variable with the same name as a arg gets declared in the function 562 | # An error should be raised. 563 | # TODO: Check in the function declaration if any of the arguments are already declared as global variables 564 | 565 | # Move the variables that were declared global in that function 566 | # to gvars(or the modules global vars) 567 | for name, value in items(globalVars.dValue): 568 | if name notin tempArgNames: 569 | if module == nil: 570 | if gvars.getVar(name) == nil: 571 | gvars.declVar(name) 572 | gvars.setVar(name, value) 573 | else: 574 | if gvars.getVar(name) == nil: 575 | module[2].declVar(name) 576 | module[2].setVar(name, value) 577 | 578 | proc getModule(name: string, gvars: var PType): seq[PType] = 579 | var modulesVar = gvars.getVar("__modules__") 580 | for module in items(modulesVar.lValue): 581 | if module.kind == ntList: 582 | if module.lValue[0].kind == ntString: 583 | if module.lValue[0].value == name: 584 | return module.lValue 585 | else: 586 | raise newException(RuntimeError, errorLine() & 587 | "Error: Invalid type, expected ntString got " & 588 | $module.lValue[0].kind) 589 | 590 | return nil 591 | 592 | proc loadModules(modules: seq[string], vars, gvars: var PType) = 593 | var paths = gvars.getVar("__path__") 594 | var modulesVar = gvars.getVar("__modules__") 595 | var loaded = false 596 | if paths != nil and modulesVar != nil: 597 | for module in items(modules): 598 | # Check if the module exists 599 | if getModule(module, gvars) != nil: 600 | raise newException(RuntimeError, errorLine() & "Error: Unable to load " & 601 | module & ", module is already loaded.") 602 | 603 | for path in items(paths.lValue): 604 | if path.kind == ntString: 605 | var file = readFile(path.value / module & ".nael") 606 | if file != nil: 607 | var locals = newVariables() 608 | var globals = newVariables() 609 | globals.addStandard() 610 | var moduleStack = newStack(200) 611 | 612 | # Add the folder where this module resides, to this modules __path__ 613 | if splitFile(module).dir != "": 614 | var modulePath = globals.getVar("__path__") 615 | modulePath.lValue.add(newString(path.value / splitFile(module).dir)) 616 | globals.setVar("__path__", modulePath) 617 | 618 | var ast = parse(file) 619 | interpret(ast, moduleStack, locals, globals) 620 | 621 | var moduleList = newList(@[newString(extractFilename(module)), locals, globals]) # [name, {locals}, {globals}] 622 | modulesVar.lValue.add(moduleList) 623 | loaded = true 624 | 625 | else: 626 | raise newException(RuntimeError, errorLine() & 627 | "Error: Unable to load " & module & 628 | ", incorrect path, got type " & $path.kind) 629 | 630 | else: 631 | raise newException(RuntimeError, errorLine() & 632 | "Error: Unable to load module, path and/or modules variable is not declared.") 633 | 634 | 635 | if not loaded: 636 | raise newException(RuntimeError, errorLine() & 637 | "Error: Unable to load module(module cannot be found).") 638 | 639 | proc includeModules(modules: seq[string], dataStack: var TStack, vars, gvars: var PType) = 640 | var paths = gvars.getVar("__path__") 641 | var modulesVar = gvars.getVar("__modules__") 642 | var loaded = false 643 | if paths != nil and modulesVar != nil: 644 | for module in items(modules): 645 | for path in items(paths.lValue): 646 | if path.kind == ntString: 647 | var file = readFile(path.value / module & ".nael") 648 | if file != nil: 649 | var ast = parse(file) 650 | 651 | # Add the folder where this module resides 652 | if splitFile(module).dir != "": 653 | paths.lValue.add(newString(path.value / splitFile(module).dir)) 654 | gvars.setVar("__path__", paths) 655 | 656 | interpret(ast, dataStack, vars, gvars) 657 | loaded = true 658 | break 659 | else: 660 | raise newException(RuntimeError, errorLine() & 661 | "Error: Unable to load " & module & 662 | ", incorrect path, got type " & $path.kind) 663 | 664 | else: 665 | raise newException(RuntimeError, errorLine() & 666 | "Error: Unable to load module, path and/or modules variable is not declared.") 667 | 668 | if not loaded: 669 | raise newException(RuntimeError, errorLine() & 670 | "Error: Unable to load module(module cannot be found).") 671 | 672 | 673 | -------------------------------------------------------------------------------- /src/interpreter.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2010 (C) Dominik Picheta - Read license.txt for the license. 2 | # 30 May 2010 3 | 4 | # Interpreter - Executes the AST 5 | import parser, strutils, os, times, math, dynlib 6 | 7 | type 8 | TDict* = seq[tuple[name: string, value: PType]] 9 | 10 | TTypes* = enum 11 | ntInt, 12 | ntFloat, 13 | ntFraction, 14 | ntString, 15 | ntBool, 16 | ntList, 17 | ntQuot, 18 | ntDict, 19 | ntNil, 20 | ntCmnd, # Exclusive to quotations. 21 | ntVar, # A pointer to a variable 22 | ntFunc, # Exclusively used only in variables 23 | ntASTNode, # A PNaelNode - VarDeclar etc, Are declared with this in quotations 24 | ntType, # Stores type information 25 | ntObject 26 | 27 | PType* = ref TType 28 | TType* = object 29 | case kind*: TTypes 30 | of ntInt: 31 | iValue*: int64 32 | of ntFloat: 33 | fValue*: float64 34 | of ntFraction: 35 | firstVal*: int64 36 | secondVal*: int64 37 | of ntString, ntCmnd: 38 | value*: string # for ntCmnd, name of cmnd. 39 | of ntBool: 40 | bValue*: bool 41 | of ntList, ntQuot: 42 | lValue*: seq[PType] 43 | of ntDict: 44 | dValue*: TDict 45 | of ntNil: nil 46 | of ntFunc: 47 | args*: seq[PType] 48 | quot*: PType 49 | of ntASTNode: 50 | node*: PNaelNode 51 | of ntVar: 52 | vvalue*: string 53 | loc*: int # 0 for local, 1 for global 54 | val*: PType 55 | of ntType: 56 | name*: string 57 | fields*: seq[string] 58 | of ntObject: 59 | typ*: PType # the ntType 60 | oFields*: TDict 61 | 62 | TStack* = tuple[stack: seq[PType], limit: int] 63 | 64 | RuntimeError* = object of Exception 65 | 66 | # Characters that are invalid in variable names 67 | var invalidVars = {'(', ')', '[', ']', '{', '}', '\"', '\''} 68 | 69 | var currentLine = 0 # The line number of the code being executed 70 | var currentChar = 0 # The char number of the code being executed 71 | 72 | proc errorLine(): string = 73 | result = "[$1, $2] " % [$currentLine, $currentChar] 74 | 75 | # Stack 76 | proc toString*(item: PType, stack = false): string = 77 | result = "" 78 | 79 | case item.kind 80 | of ntInt: 81 | return $item.iValue 82 | of ntFloat: 83 | return $item.fValue 84 | of ntFraction: 85 | return $item.firstVal & "/" & $item.secondVal 86 | of ntString, ntCmnd: 87 | if stack: 88 | return "\"" & item.value & "\"" 89 | else: 90 | return item.value 91 | 92 | of ntBool: 93 | return $item.bValue 94 | 95 | of ntList: 96 | result.add("[") 97 | for i in 0 .. len(item.lValue)-1: 98 | result.add(toString(item.lValue[i], stack)) 99 | if i < len(item.lValue)-1: 100 | result.add(", ") 101 | result.add("]") 102 | 103 | of ntQuot: 104 | result.add("(") 105 | for i in 0 .. len(item.lValue)-1: 106 | result.add(toString(item.lValue[i], stack)) 107 | if i < len(item.lValue)-1: 108 | result.add(", ") 109 | result.add(")") 110 | of ntDict: 111 | result.add("__dict__") 112 | of ntNil: 113 | result.add("nil") 114 | of ntVar: 115 | result.add("") 116 | of ntFunc: 117 | result.add("__func__") 118 | of ntAstNode: 119 | case item.node.kind: 120 | of nnkVarDeclar: 121 | result.add(item.node.value & " let") 122 | of nnkFunc: 123 | result.add("Func(" & item.node.fName & ")") 124 | else: 125 | raise newException(RuntimeError, errorLine() & "Error: Unexpected AstNode in `$`, " & $item.node.kind) 126 | of ntType: 127 | result.add("") 128 | of ntObject: 129 | result.add("") 130 | 131 | proc `$`*(kind: TTypes): string = 132 | case kind 133 | of ntInt: return "int" 134 | of ntFloat: return "float" 135 | of ntFraction: return "fraction" 136 | of ntString: return "string" 137 | of ntBool: return "bool" 138 | of ntList: return "list" 139 | of ntQuot: return "quot" 140 | of ntDict: return "dict" 141 | of ntNil: return "nil" 142 | of ntCmnd: return "__cmnd__" 143 | of ntVar: return "var" 144 | of ntFunc: return "__func__" 145 | of ntASTNode: return "__ASTNode__" 146 | of ntType: return "type" 147 | of ntObject: return "object" 148 | 149 | proc isEqual*(first, second: PType): bool = 150 | if first.kind == second.kind: 151 | case first.kind 152 | of ntInt: 153 | return first.iValue == second.iValue 154 | of ntFloat: 155 | return first.fValue == second.fValue 156 | of ntFraction: 157 | return first.firstVal == second.firstVal and first.secondVal == second.secondVal 158 | of ntString, ntCmnd, ntVar: 159 | return first.value == second.value 160 | of ntBool: 161 | return first.bValue == second.bValue 162 | of ntList, ntQuot: 163 | if len(first.lValue) == len(second.lValue): 164 | for i in 0 .. len(first.lValue)-1: 165 | if not isEqual(first.lValue[i], second.lValue[i]): 166 | return false 167 | return true 168 | else: return false 169 | of ntDict, ntFunc, ntAstNode, ntType, ntObject: 170 | return first == second # FIXME: This probably doesn't work. 171 | of ntNil: return true 172 | else: return false 173 | 174 | proc printStack*(stack: TStack) = 175 | if stack.stack.len() > 0: 176 | var result = "stack → [" 177 | for i in 0 .. len(stack.stack)-1: 178 | result.add(toString(stack.stack[i], true)) 179 | if i < len(stack.stack)-1: 180 | result.add(", ") 181 | 182 | result.add("]") 183 | echo(result) 184 | 185 | proc newStack*(limit: int): TStack = 186 | result.stack = @[] 187 | result.limit = limit 188 | 189 | proc push*(stack: var TStack, item: PType) = 190 | if stack.stack.len() >= stack.limit: 191 | raise newException(OverflowError, errorLine() & "Error: Stack overflow") 192 | 193 | stack.stack.add(item) 194 | 195 | proc pop*(stack: var TStack): PType = 196 | if stack.stack.len() < 1: 197 | raise newException(OverflowError, errorLine() & "Error: Stack underflow") 198 | 199 | return stack.stack.pop() 200 | 201 | proc newInt*(value: int64): PType = 202 | new(result) 203 | result.kind = ntInt 204 | result.iValue = value 205 | 206 | proc newFloat*(value: float64): PType = 207 | new(result) 208 | result.kind = ntFloat 209 | result.fValue = value 210 | 211 | proc newFraction*(fValue: int64, sValue: int64): PType = 212 | new(result) 213 | result.kind = ntFraction 214 | result.firstVal = fValue 215 | result.secondVal = sValue 216 | 217 | proc newString*(value: string): PType = 218 | new(result) 219 | result.kind = ntString 220 | result.value = value 221 | 222 | proc newBool*(value: bool): PType = 223 | new(result) 224 | result.kind = ntBool 225 | result.bValue = value 226 | 227 | proc newList*(value: seq[PType]): PType = 228 | new(result) 229 | result.kind = ntList 230 | result.lValue = value 231 | 232 | proc newCmnd*(value: string): PType = 233 | new(result) 234 | result.kind = ntCmnd 235 | result.value = value 236 | 237 | proc toPType(item: PNaelNode): PType 238 | proc newFunc*(node: PNaelNode): PType = 239 | new(result) 240 | result.kind = ntFunc 241 | var args: seq[PType] = @[] 242 | for n in items(node.args.children): 243 | if n.kind == nnkCommand: 244 | args.add(newCmnd(n.value)) 245 | else: 246 | raise newException(RuntimeError, errorLine() & 247 | "Error: Function declaration incorrect, got " & $n.kind & " for args") 248 | 249 | result.args = args 250 | result.quot = toPType(node.quot) 251 | 252 | proc newType*(node: PNaelNode): PType = 253 | new(result) 254 | result.kind = ntType 255 | result.name = node.tName 256 | 257 | var fields: seq[string] = @[] 258 | for n in items(node.fields.children): 259 | if n.kind == nnkCommand: 260 | fields.add(n.value) 261 | else: 262 | raise newException(RuntimeError, errorLine() & 263 | "Error: Tuple declaration incorrect, got " & $n.kind & " for fields") 264 | 265 | result.fields = fields 266 | 267 | proc newObject*(typ: PType, fields: TDict): PType = 268 | new(result) 269 | result.kind = ntObject 270 | result.typ = typ 271 | result.oFields = fields 272 | 273 | proc newVar*(name: string, loc: int, val: PType): PType = 274 | new(result) 275 | result.kind = ntVar 276 | result.vvalue = name 277 | result.loc = loc 278 | result.val = val 279 | 280 | proc newASTNode*(node: PNaelNode): PType = 281 | new(result) 282 | result.kind = ntASTNode 283 | result.node = node 284 | 285 | proc newNil*(): PType = 286 | new(result) 287 | result.kind = ntNil 288 | 289 | proc newVariables*(): PType = 290 | new(result) 291 | result.kind = ntDict 292 | result.dValue = @[] 293 | 294 | proc includeModules(modules: seq[string], dataStack: var TStack, vars, gvars: var PType) 295 | proc addStandard*(vars: var PType) = 296 | ## Adds standard variables(__path__, __modules__) to vars. 297 | var appDir = newString(os.getAppDir()) 298 | var pathVar = newList(@[appDir, newString(appDir.value / "lib")]) 299 | vars.dValue.add(("__path__", pathVar)) 300 | 301 | var modulesVar: PType # [["name", {locals}, {globals}], ...] 302 | modulesVar = newList(@[]) 303 | vars.dValue.add(("__modules__", modulesVar)) 304 | 305 | vars.dValue.add(("false", newBool(false))) 306 | vars.dValue.add(("true", newBool(true))) 307 | 308 | proc loadStdlib*(dataStack: var TStack, vars, gvars: var PType) = 309 | # Load the system module 310 | includeModules(@["system"], dataStack, vars, gvars) 311 | 312 | proc getVar*(vars: var PType, name: string): PType = 313 | if vars.kind != ntDict: 314 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 315 | 316 | for vname, value in items(vars.dValue): 317 | if vname == name: 318 | return value 319 | 320 | return nil 321 | 322 | proc getVarIndex*(vars: var PType, name: string): int = 323 | if vars.kind != ntDict: 324 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 325 | 326 | for i in 0 .. len(vars.dValue)-1: 327 | if vars.dValue[i][0] == name: 328 | return i 329 | 330 | return -1 331 | 332 | proc getVarField*(vars: var PType, name: string): PType = 333 | var s = name.split('.') 334 | 335 | var tVar = vars.getVar(s[0]) 336 | 337 | if tVar != nil: 338 | if tVar.kind == ntObject: 339 | # Check if the field exists 340 | for fieldName, value in items(tVar.oFields): 341 | if fieldName == s[1]: 342 | return value 343 | 344 | return nil 345 | 346 | proc getVarFieldIndex*(vars: var PType, name: string): array[0..1, int] = # [var index, field index] 347 | var s = name.split('.') 348 | 349 | var tVarIndex = vars.getVarIndex(s[0]) 350 | var tVar = vars.dValue[tVarIndex][1] 351 | 352 | if tVarIndex != -1: 353 | if tVar.kind == ntObject: 354 | # Check if the field exists 355 | for i in 0 .. len(tVar.oFields)-1: 356 | if tVar.oFields[i][0] == s[1]: 357 | return [tVarIndex, i] 358 | 359 | return [-1,-1] 360 | 361 | proc declVar*(vars: var PType, name: string) = 362 | # Declares a variable 363 | for i in items(name): 364 | if i in invalidVars: 365 | raise newException(RuntimeError, errorLine() & "Error: Variable name contains illegal characters.") 366 | 367 | if vars.kind != ntDict: 368 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 369 | 370 | if getVarIndex(vars, name) != -1: 371 | raise newException(RuntimeError, errorLine() & "Error: $1 is already declared." % [name]) 372 | 373 | var theVar = newNil() 374 | 375 | vars.dValue.add((name, theVar)) 376 | 377 | proc setVar*(vars: var PType, name: string, theVar: PType) = 378 | # Sets a variables value. 379 | if vars.kind != ntDict: 380 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 381 | 382 | var varIndex = vars.getVarIndex(name) 383 | if varIndex == -1: 384 | raise newException(RuntimeError, errorLine() & "Error: Variable $1 is not declared." % [name]) 385 | 386 | vars.dValue[varIndex][1] = theVar 387 | 388 | proc setVarField*(vars: var PType, name: string, theVar: PType) = 389 | # Sets a variables value. 390 | if vars.kind != ntDict: 391 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 392 | 393 | var varIndex = vars.getVarFieldIndex(name) 394 | if varIndex[0] == -1 or varIndex[1] == -1: 395 | raise newException(RuntimeError, errorLine() & "Error: Variable $1 is not declared." % [name]) 396 | 397 | vars.dValue[varIndex[0]][1].oFields[varIndex[1]][1] = theVar 398 | 399 | proc remVar*(vars: var PType, name: string) = 400 | if vars.kind != ntDict: 401 | raise newException(ValueError, errorLine() & "Error: The variable list needs to be a dict.") 402 | 403 | for i in 0 .. len(vars.dValue)-1: 404 | if vars.dValue[i][0] == name: 405 | vars.dValue.del(i) 406 | return 407 | 408 | raise newException(RuntimeError, errorLine() & "Error: Unable to remove variable, it doesn't exist.") 409 | 410 | proc copyVar*(tVar: PType): PType = 411 | new(result) 412 | result.kind = tVar.kind 413 | case tVar.kind 414 | of ntList: 415 | result.lValue = @[] 416 | for i in 0 .. len(tVar.lValue)-1: 417 | result.lValue.add(tVar.lValue[i]) 418 | of ntQuot: 419 | result.lValue = @[] 420 | for i in 0 .. len(tVar.lValue)-1: 421 | result.lValue.add(tVar.lValue[i]) 422 | of ntDict: 423 | result.dValue = @[] 424 | for i in 0 .. len(tVar.dValue)-1: 425 | result.dValue.add(tVar.dValue[i]) 426 | of ntVar: 427 | result.vvalue = tVar.vvalue 428 | result.loc = tVar.loc 429 | result.val = tVar.val 430 | of ntFunc: 431 | result.args = tVar.args 432 | result.quot = tVar.quot # I doubt there will be any functions for editing this 433 | of ntAstNode, ntType, ntInt, ntFloat, ntFraction, ntCmnd, ntString, ntBool, ntNil: 434 | result = tVar 435 | of ntObject: 436 | result.typ = tVar.typ 437 | result.oFields = tVar.oFields 438 | 439 | proc toPType(item: PNaelNode): PType = 440 | ## Converts a PNaelNode of any kind. Into a corresponding PType 441 | new(result) 442 | 443 | case item.kind 444 | of nnkIntLit: 445 | result = newInt(item.iValue) 446 | of nnkStringLit: 447 | result = newString(item.value) 448 | of nnkFloatLit: 449 | result = newFloat(item.fValue) 450 | of nnkListLit: 451 | result.kind = ntList 452 | result.lValue = @[] 453 | of nnkQuotLit: 454 | result.kind = ntQuot 455 | result.lValue = @[] 456 | else: 457 | raise newException(SystemError, errorLine() & "Error: Unexpected nael node kind, " & $item.kind) 458 | 459 | # Add all the children of quotations and lists. 460 | if item.kind == nnkListLit or item.kind == nnkQuotLit: 461 | for node in items(item.children): 462 | case node.kind 463 | of nnkCommand, nnkVarDeclar, nnkFunc, nnkTuple: 464 | # Commands are not allowed in Lists. 465 | if item.kind == nnkListLit: 466 | raise newException(RuntimeError, errorLine() & 467 | "Error: $1 not allowed in a list literal" % [$node.kind]) 468 | 469 | elif item.kind == nnkQuotLit: 470 | if node.kind == nnkCommand: 471 | result.lValue.add(newCmnd(node.value)) 472 | elif node.kind == nnkVarDeclar or node.kind == nnkFunc: 473 | result.lValue.add(newASTNode(node)) 474 | 475 | of nnkIntLit, nnkStringLit, nnkFloatLit, nnkListLit, nnkQuotLit: 476 | result.lValue.add(toPType(node)) 477 | 478 | proc command*(cmnd: string, dataStack: var TStack, vars, gvars: var PType) # from core.nim 479 | proc interpretQuotation*(quot: PType, dataStack: var TStack, vars, gvars: var PType) # from core.nim 480 | 481 | proc interpret*(ast: seq[PNaelNode], dataStack: var TStack, vars, gvars: var PType) = 482 | # Reset the values 483 | currentLine = 0 484 | currentChar = 0 485 | for node in items(ast): 486 | # Set these before any execution begins, 487 | # so that when errors occur they get the right info. 488 | currentLine = node.lineNum 489 | currentChar = node.charNum 490 | 491 | # REMEMBER ANY CHANGES YOU MAKE HERE, HAVE TO BE MADE IN interpretQuotation 492 | case node.kind 493 | of nnkCommand: 494 | command(node.value, dataStack, vars, gvars) 495 | of nnkIntLit, nnkStringLit, nnkFloatLit, nnkListLit, nnkQuotLit: 496 | dataStack.push(toPType(node)) 497 | of nnkVarDeclar: 498 | if gvars.getVar(node.value) != nil: 499 | raise newException(RuntimeError, errorLine() & 500 | "Error: $1 is already declared as a global variable" % [node.value]) 501 | else: 502 | vars.declVar(node.value) 503 | of nnkFunc: 504 | if gvars.getVarIndex(node.fName) == -1: 505 | gvars.declVar(node.fName) 506 | gvars.setVar(node.fName, newFunc(node)) 507 | of nnkTuple: 508 | if gvars.getVarIndex(node.tName) == -1: 509 | gvars.declVar(node.tName) 510 | gvars.setVar(node.tName, newType(node)) 511 | 512 | include core 513 | 514 | proc exec*(code: string, dataStack: var TStack, vars, gvars: var PType) = 515 | var ast = parse(code) 516 | interpret(ast, dataStack, vars, vars) 517 | 518 | var dataStack = newStack(200) 519 | var vars = newVariables() # 'main' variables(They act as both local and global) 520 | vars.addStandard() 521 | loadStdlib(dataStack, vars, vars) 522 | 523 | when isMainModule: 524 | var t = getStartmilsecs() 525 | var ast = parse("x let, x 5 5 + = x") 526 | interpret(ast, dataStack, vars, vars) 527 | printStack(dataStack) 528 | echo("Time taken, ", getStartmilsecs() - t) 529 | -------------------------------------------------------------------------------- /src/lexer.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2010 (C) Dominik Picheta - Read license.txt for the license. 2 | # 29 May 2010 3 | 4 | # Lexical analyser 5 | 6 | type 7 | TTokens = tuple[token: string, curLine, curChar: int] 8 | 9 | 10 | proc analyse*(code: string): seq[TTokens] = 11 | var currentLine = 0 12 | var currentChar = 0 13 | 14 | 15 | result = @[] 16 | 17 | var r = "" 18 | 19 | var i = 0 20 | while true: 21 | if code.len == i: 22 | if r != "": 23 | result.add((r, currentLine, currentChar)) 24 | break 25 | 26 | case code[i] 27 | of ' ', ',', '\L', '\c': # Chars to ignore, these also mark the end of a token 28 | if r != "": 29 | result.add((r, currentLine, currentChar)) 30 | r = "" 31 | 32 | if code[i] == '\L' or code[i] == '\c': 33 | inc(currentLine) 34 | currentChar = 0 35 | 36 | of '[', '(': 37 | # Add any token which is left. 38 | if r != "": 39 | result.add((r, currentLine, currentChar)) 40 | r = "" 41 | 42 | # Add [ or ( as a seperate token. 43 | result.add(($code[i], currentLine, currentChar)) 44 | 45 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 46 | inc(i) # Skip the [ or ( 47 | 48 | var opMet = 1 # The number of times [ or ( was matched. 49 | # This gets decreased when ] or ) is met. 50 | while true: 51 | if code.len == i: 52 | if r != "": 53 | result.add((r, currentLine, currentChar)) 54 | return 55 | 56 | case code[i] 57 | of '[', '(': 58 | inc(opMet) 59 | r.add($code[i]) 60 | of ']', ')': 61 | if opMet == 1: 62 | # Add everything between ( and ) or [ and ] 63 | result.add((r, currentLine, currentChar)) 64 | r = "" 65 | 66 | # Add ) or ] 67 | result.add(($code[i], currentLine, currentChar)) 68 | break 69 | else: 70 | dec(opMet) 71 | r.add($code[i]) 72 | 73 | of '\"': 74 | # Add the " first 75 | r.add($code[i]) 76 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 77 | inc(i) # Then Skip the starting " 78 | while true: 79 | if code[i] == '\"': 80 | r.add($code[i]) 81 | break 82 | else: 83 | r.add($code[i]) 84 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 85 | inc(i) 86 | 87 | else: 88 | r = r & code[i] 89 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 90 | inc(i) 91 | 92 | 93 | of ')', ']': 94 | # Add these as seperate tokens 95 | if r != "": 96 | result.add((r, currentLine, currentChar)) 97 | r = "" 98 | 99 | result.add(($code[i], currentLine, currentChar)) 100 | 101 | of '\"': 102 | # Add any token which is waiting to get added 103 | if r != "": 104 | result.add((r, currentLine, currentChar)) 105 | r = "" 106 | 107 | # Add " as a seperate token 108 | result.add(($code[i], currentLine, currentChar)) 109 | 110 | # skip the " 111 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 112 | inc(i) 113 | 114 | while true: 115 | if code.len == i: 116 | if r != "": 117 | result.add((r, currentLine, currentChar)) 118 | return 119 | 120 | case code[i] 121 | of '\"': 122 | result.add((r, currentLine, currentChar)) 123 | r = "" 124 | 125 | result.add(($code[i], currentLine, currentChar)) 126 | break 127 | else: 128 | r.add($code[i]) 129 | 130 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 131 | inc(i) 132 | 133 | of '#': 134 | # Add any token which is waiting to get added 135 | if r != "": 136 | result.add((r, currentLine, currentChar)) 137 | r = "" 138 | 139 | while true: 140 | if code.len == i: 141 | return 142 | case code[i] 143 | of '\L', '\c': 144 | inc(currentLine) 145 | currentChar = 0 146 | break 147 | else: 148 | discard 149 | inc(currentChar) # When increasing i, currentChar needs to be increased aswell 150 | inc(i) 151 | 152 | else: 153 | r = r & code[i] 154 | 155 | inc(currentChar) 156 | inc(i) 157 | 158 | when isMainModule: 159 | for i, cL, cC in items(analyse("func foo [arg] (((\"test\" print) call) call);")): 160 | if i != "": 161 | echo(i) 162 | else: 163 | echo("<>EMPTY<>") 164 | -------------------------------------------------------------------------------- /src/lib/math.nael: -------------------------------------------------------------------------------- 1 | # nael math module 2 | # Copyright 2010 Dominik Picheta, Alex Mitchell, Vijay Vanness 3 | 4 | # Constants 5 | func pi [] (3.14159265); 6 | func e [] (2.71828183); 7 | 8 | # Tuples 9 | point [x,y] tuple 10 | ratio [numberator,denominator] tuple 11 | 12 | # Function 13 | 14 | func picks-basic [area perimeter] ( 15 | #Allows you to calculate the area of a polygon in square units : see http://en.wikipedia.org/wiki/Pick's_theorem 16 | (area get 17 | (perimeter get 2 /) 18 | call +) 19 | call 1 - 20 | ); 21 | 22 | func circum [r] ( 23 | # Calculates the circumference of r 24 | r get >float 2.0 *, pi * 25 | ); 26 | 27 | func fib [n] ( 28 | first let, first 0 = 29 | second let, second 1 = 30 | t let, t 0 = 31 | (n get 1 >) 32 | (t first get second get + =, first second get =, second t get =, n n get 1 - =) 33 | while 34 | second get 35 | ); 36 | 37 | func isEven? [n] (n get 2 % 0 ==); 38 | 39 | func collatz [c] ( 40 | (c get isEven?) 41 | (c get 2 /) 42 | (c get 3 * 1 +) if 43 | ); 44 | 45 | func midpoint [x1 y1 x2 y2] ( 46 | ar let, ar [] = 47 | ar get 48 | x1 get >float, x2 get >float + 2.0 / append 49 | ar get y1 get >float y2 get >float + 2.0 / append 50 | del 51 | ); 52 | 53 | func distance [x1 y1 x2 y2] ( 54 | ( 55 | ((x2 get >float x1 get >float -) call >float 2.0 pow) call 56 | ((y2 get >int y1 get >int -) call >float 2.0 pow) call 57 | +) call >float sqrt 58 | ); 59 | 60 | func heaviside [n] ( 61 | (n get 0 >) 62 | (1) 63 | ((n get 0 ==) (0.5) (0) if) if 64 | ); 65 | 66 | func absolute [n] ( 67 | n get >float n get >float * sqrt 68 | ); 69 | 70 | func >float [n] (("int" n get type ==) (n get int>float) (("float" n get type ==) (n get) ("Incompatible type") if) if); 71 | func >int [n] (("float" n get type ==) (n get float>int) (("int" n get type ==) (n get) ("Incompatible type") if) if); 72 | 73 | func max [n] ( 74 | n get 0.5 (n get 0.0 >=) (+) (-) if float>int 75 | ); 76 | 77 | func slope-intercept-recurs [a b x1] ( #The same as the one below, except, this except x1 to be a [] list of numbers. 78 | (x1 get 79 | (b get *) 80 | each) 81 | call a get + 82 | ); 83 | 84 | func slope-intercept [a b x] ( 85 | (x get 86 | b get 87 | *) 88 | call a get + 89 | ); 90 | #to some people, know as the opposite-reciprocal. 91 | func reciprocal [point] ( 92 | final let, 93 | final [] =, 94 | final get 0 point get 1 nth - append, 95 | point get 0 nth append 96 | ); 97 | -------------------------------------------------------------------------------- /src/lib/pythag.nael: -------------------------------------------------------------------------------- 1 | "math" import 2 | func cb [c b] (((c get 2.0 pow) call (b get 2.0 pow) call -) call sqrt); 3 | func ab [a b] (((a get 2.0 pow) call (b get 2.0 pow) call +) call sqrt); 4 | -------------------------------------------------------------------------------- /src/main.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2010 (C) Dominik Picheta - Read license.txt for the license. 2 | # 30 May 2010 3 | 4 | # REPL - A nice REPL for nael. 5 | import interpreter, os 6 | 7 | when isMainModule: 8 | if paramCount() == 0 or paramCount() == -1: 9 | var dataStack = newStack(200) 10 | var vars = newVariables() # 'main' variables(They act as both local and global) 11 | vars.addStandard() 12 | loadStdlib(dataStack, vars, vars) # Load system. 13 | 14 | while true: 15 | stdout.write(">> ") 16 | try: 17 | exec(stdin.readLine(), dataStack, vars, vars) 18 | except RuntimeError, OverflowError, ValueError, SystemError: 19 | echo(getCurrentExceptionMsg()) 20 | 21 | 22 | printStack(dataStack) 23 | else: 24 | if paramStr(1) == "-a" or paramStr(1) == "--about": 25 | echo(" nael interpreter v0.1") 26 | echo(" ---------------------") 27 | echo("http://github.com/dom96/nael") 28 | echo("For license read license.txt") 29 | discard stdin.readLine() 30 | 31 | elif paramStr(1) == "-h" or paramStr(1) == "--help" or paramCount() > 0: 32 | echo("nael Interpreter") 33 | echo("nael -noDecoration Starts the interpreter, without >>") 34 | echo("nael -h[--help] This help message") 35 | echo("nael -a[--about] About") 36 | 37 | -------------------------------------------------------------------------------- /src/parser.nim: -------------------------------------------------------------------------------- 1 | # Copyright 2010 (C) Dominik Picheta - Read license.txt for the license. 2 | # 29 May 2010 3 | 4 | # Syntactic analyser - Calls the lexer and turns it's output into an AST 5 | import lexer, strutils 6 | 7 | type 8 | PNaelNodeKind* = ref TNaelNodeKind 9 | 10 | TNaelNodeKind* = enum 11 | nnkCommand, # This might be a var, or a function like `print` 12 | nnkStringLit, # "string" 13 | nnkIntLit, # 1 14 | nnkFloatLit, # 1.0 15 | nnkListLit, # [5] 16 | nnkQuotLit, # ("some code here" print) 17 | nnkVarDeclar, # x let 18 | nnkFunc, # func foo [args] (...); 19 | nnkTuple # name [ fields ] tuple 20 | 21 | PNaelNode* = ref TNaelNode 22 | 23 | TNaelNode* = object 24 | lineNum*: int 25 | charNum*: int 26 | case kind*: TNaelNodeKind 27 | of nnkListLit, nnkQuotLit: 28 | children*: seq[PNaelNode] 29 | of nnkCommand, nnkStringLit, nnkVarDeclar: 30 | value*: string 31 | of nnkFunc: 32 | fName*: string 33 | args*: PNaelNode # List 34 | quot*: PNaelNode # Quotation 35 | of nnkTuple: 36 | tName*: string 37 | fields*: PNaelNode # List 38 | of nnkIntLit: 39 | iValue*: int64 40 | of nnkFloatLit: 41 | fValue*: float64 42 | 43 | proc getChar(tokens: seq[string], i: int): int = 44 | result = 0 45 | for t in 0 .. len(tokens)-1: 46 | inc(result, tokens[t].len()) 47 | 48 | proc tokenIsNumber(token: string): bool = 49 | if token == "-": 50 | return false 51 | 52 | for i in items(token): 53 | if i in {'0'..'9'}: 54 | result = true 55 | elif i == '-': 56 | result = false 57 | else: 58 | return false 59 | 60 | proc tokenIsFloat(token: string): bool = 61 | var dot = false 62 | var nr = false 63 | for i in items(token): 64 | if i in {'0'..'9'}: 65 | nr = true 66 | elif i == '.': 67 | dot = true 68 | elif i == '-': 69 | discard 70 | else: 71 | return false 72 | 73 | if dot and nr: 74 | return true 75 | 76 | proc parse*(code: string): seq[PNaelNode] = 77 | # Parse code into an AST 78 | result = @[] 79 | 80 | var tokens = analyse(code) # (token, lineNum, charNum) 81 | 82 | var i = 0 83 | while true: 84 | if tokens.len()-1 < i: 85 | break 86 | 87 | case tokens[i][0] 88 | of "(": 89 | # Everything in between ( and ) is one token. 90 | # If an empty quot is present, (), then the token between is an empty string "" 91 | if tokens.len()-i > 2 and tokens[i + 2][0] == ")": 92 | var quotNode: PNaelNode 93 | new(quotNode) 94 | quotNode.lineNum = tokens[i][1] 95 | quotNode.charNum = tokens[i][2] 96 | quotNode.kind = nnkQuotLit 97 | quotNode.children = parse(tokens[i + 1][0]) 98 | 99 | # Skip the quotation and ')' 100 | inc(i, 2) 101 | 102 | result.add(quotNode) 103 | else: 104 | raise newException(SystemError, 105 | "[Line: $1 Char: $2] SyntaxError: Quotation not ended" % 106 | [$tokens[i][1], $tokens[i][2]]) 107 | 108 | of "[": 109 | # Everything in between [ and ] is one token. 110 | # If an empty list is present, [], then the token between is an empty string "" 111 | if tokens.len()-i > 2 and tokens[i + 2][0] == "]": 112 | var listNode: PNaelNode 113 | new(listNode) 114 | listNode.lineNum = tokens[i][1] 115 | listNode.charNum = tokens[i][2] 116 | listNode.kind = nnkListLit 117 | listNode.children = parse(tokens[i + 1][0]) 118 | 119 | # skip the list and ']' 120 | inc(i, 2) 121 | 122 | result.add(listNode) 123 | else: 124 | raise newException(SystemError, 125 | "[Line: $1 Char: $2] SyntaxError: List not ended" % 126 | [$tokens[i][1], $tokens[i][2]]) 127 | 128 | of "]", ")": 129 | if tokens[i][0] == "]": 130 | raise newException(SystemError, 131 | "[Line: $1 Char: $2] SyntaxError: List not started" % 132 | [$tokens[i][1], $tokens[i][2]]) 133 | elif tokens[i][0] == ")": 134 | raise newException(SystemError, 135 | "[Line: $1 Char: $2] SyntaxError: Quotation not started" % 136 | [$tokens[i][1], $tokens[i][2]]) 137 | 138 | of "\"": 139 | # Everything in between " and " is one token. 140 | if tokens.len()-i > 2 and tokens[i + 2][0] == "\"": 141 | var strNode: PNaelNode 142 | new(strNode) 143 | strNode.lineNum = tokens[i][1] 144 | strNode.charNum = tokens[i][2] 145 | strNode.kind = nnkStringLit 146 | strNode.value = tokens[i + 1][0] 147 | 148 | # skip the string and " 149 | inc(i, 2) 150 | 151 | result.add(strNode) 152 | else: 153 | raise newException(SystemError, 154 | "[Line: $1 Char: $2] SyntaxError: String not ended" % 155 | [$tokens[i][1], $tokens[i][2]]) 156 | 157 | of "func": 158 | if tokens.len()-i > 8 and tokens[i + 8][0] == ";": 159 | # each [ is one token, same goes for (, ] and ] 160 | # func foo [args] (...); 161 | var funcNode: PNaelNode 162 | new(funcNode) 163 | funcNode.lineNum = tokens[i][1] 164 | funcNode.charNum = tokens[i][2] 165 | funcNode.kind = nnkFunc 166 | funcNode.fName = tokens[i + 1][0] 167 | funcNode.args = parse(tokens[i + 2][0] & tokens[i + 3][0] & tokens[i + 4][0])[0] 168 | funcNode.quot = parse(tokens[i + 5][0] & tokens[i + 6][0] & tokens[i + 7][0])[0] 169 | 170 | inc(i, 8) 171 | 172 | result.add(funcNode) 173 | else: 174 | raise newException(SystemError, 175 | "[Line: $1 Char: $2] SyntaxError: Invalid function declaration" % 176 | [$tokens[i][1], $tokens[i][2]]) 177 | 178 | else: 179 | if tokenIsNumber(tokens[i][0]): 180 | var intNode: PNaelNode 181 | new(intNode) 182 | intNode.lineNum = tokens[i][1] 183 | intNode.charNum = tokens[i][2] 184 | intNode.kind = nnkIntLit 185 | intNode.iValue = tokens[i][0].parseInt() 186 | result.add(intNode) 187 | 188 | elif tokenIsFloat(tokens[i][0]): 189 | var floatNode: PNaelNode 190 | new(floatNode) 191 | floatNode.lineNum = tokens[i][1] 192 | floatNode.charNum = tokens[i][2] 193 | floatNode.kind = nnkFloatLit 194 | floatNode.fValue = tokens[i][0].parseFloat() 195 | result.add(floatNode) 196 | 197 | else: 198 | # Test for special expressions here. 199 | 200 | if tokens.len()-i > 1 and tokens[i + 1][0] == "let": 201 | # x let -> VarDeclaration(x) 202 | var declNode: PNaelNode 203 | new(declNode) 204 | declNode.lineNum = tokens[i][1] 205 | declNode.charNum = tokens[i][2] 206 | declNode.kind = nnkVarDeclar 207 | declNode.value = tokens[i][0] 208 | 209 | # Move from x to let, then the inc(i) at the bottom will move 210 | # to the token after 'let' 211 | inc(i) 212 | 213 | result.add(declNode) 214 | 215 | elif tokens.len()-i > 4 and tokens[i + 4][0] == "tuple": 216 | # each [ is one token 217 | # name [ field1 field2 ] tuple 218 | var tupleNode: PNaelNode 219 | new(tupleNode) 220 | tupleNode.lineNum = tokens[i][1] 221 | tupleNode.charNum = tokens[i][2] 222 | tupleNode.kind = nnkTuple 223 | tupleNode.tName = tokens[i][0] 224 | tupleNode.fields = parse(tokens[i + 1][0] & tokens[i + 2][0] & tokens[i + 3][0])[0] 225 | 226 | inc(i, 4) 227 | 228 | result.add(tupleNode) 229 | 230 | else: 231 | 232 | var cmndNode: PNaelNode 233 | new(cmndNode) 234 | cmndNode.lineNum = tokens[i][1] 235 | cmndNode.charNum = tokens[i][2] 236 | cmndNode.kind = nnkCommand 237 | cmndNode.value = tokens[i][0] 238 | result.add(cmndNode) 239 | 240 | inc(i) 241 | 242 | # for Debugging ONLY 243 | proc `$`*(n: PNaelNode): string = 244 | result = "" 245 | case n.kind 246 | of nnkQuotLit: 247 | result.add("QuotSTART") 248 | for i in items(n.children): 249 | result.add(" " & $i & " ") 250 | result.add("QuotEND") 251 | 252 | of nnkListLit: 253 | result.add("ListSTART") 254 | for i in items(n.children): 255 | result.add(" " & $i & " ") 256 | result.add("ListEND") 257 | of nnkCommand: 258 | result.add("CMND(" & n.value & ")") 259 | of nnkVarDeclar: 260 | result.add("VarDeclaration(" & n.value & ")") 261 | of nnkFunc: 262 | var args = $(n.args) 263 | var quot = $(n.quot) 264 | 265 | result.add("Func($1, $2, $3)" % [n.fName, args, quot]) 266 | of nnkStringLit: 267 | result.add("STR(\"" & n.value & "\")") 268 | of nnkIntLit: 269 | result.add("INT(" & $n.iValue & ")") 270 | of nnkFloatLit: 271 | result.add("FLOAT(" & $n.fValue & ")") 272 | of nnkTuple: 273 | result.add("tuple($1, $2)" % [n.tName, $(n.fields)]) 274 | 275 | proc `$`(ast: seq[PNaelNode]): string = 276 | result = "" 277 | for n in items(ast): 278 | result.add($n & "\n") 279 | 280 | when isMainModule: 281 | echo parse("func foo [arg] (print);") 282 | 283 | discard """ 284 | 285 | var t = times.getStartMilsecs() 286 | var ti = times.getTime() 287 | 288 | echo parse("x [[90], 6] =") 289 | 290 | var t1 = times.getStartMilsecs() 291 | var ti1 = times.getTime() 292 | echo("Time taken = ", t1 - t, "ms") 293 | echo("Other Time taken = ", int(ti1) - int(ti), "s")""" 294 | 295 | -------------------------------------------------------------------------------- /src/system.nael: -------------------------------------------------------------------------------- 1 | func while2 [cond, do] ((cond get call) (do get call cond get do get while2) () if); 2 | func times [do, i] (count let, count 0 =, (count get, i get, <) (do get call, count count get 1 + =) while ); 3 | func dup [] (__stack__ len 1 -, __stack__ swap nth); 4 | func clear [] ((del) __stack__ len 1 - times); 5 | func dip [value, quo] (quo get call value get); 6 | 7 | func inc [i] (i i get 1 + =, i get); 8 | 9 | func <= [first, second] (first get second get == first get second get < or); 10 | func >= [x,y] (y get x get <=); 11 | 12 | func each [list, quo] 13 | ( 14 | i let, i 0 = # Set i to 0 15 | ( 16 | # Check if the length of list is bigger than i 17 | # If not end the loop 18 | i get 19 | list get len 1 - < 20 | ) 21 | ( 22 | list get, i get nth # Get the i-th item of list 23 | quo get call # Call the quotation in the quo variable 24 | i i get inc = # Increase i by 1 25 | ) while 26 | ); 27 | 28 | func fold [list, beg, quo] 29 | ( 30 | i let, i 0 = 31 | beg2 let, beg2 beg get = 32 | ( 33 | i get 34 | list get len < 35 | ) 36 | ( 37 | beg2 get 38 | list get, i get nth 39 | quo get call 40 | beg2 swap = 41 | 42 | i i get inc = 43 | ) while 44 | beg2 get 45 | ); 46 | 47 | func join [list, seperator] (i let, i 0 =, s let, s "" =, (i get, list get len 1 - <) (s get (i get 0 == !) (seperator get +) () if, list get, i get nth, + s swap =, i i get inc =) while, s get); 48 | 49 | -------------------------------------------------------------------------------- /src/tests/funcargs.nael: -------------------------------------------------------------------------------- 1 | test [a] (a get call); 2 | test2 [t] ((t get print) test); 3 | 2 test2 4 | # Should return 2 -------------------------------------------------------------------------------- /src/tests/funcargs2.nael: -------------------------------------------------------------------------------- 1 | foo [n] (n let, n get); 2 | 56 foo # This should error, 'n is already declared as a global variable' 3 | 4 | x let 5 | foo1 [x] (x get); # No Error - But x has to be nil 6 | 7 | foo2 [s] (s get); 8 | s let 9 | 56 foo2 # No Error - But s has to stay nil 10 | -------------------------------------------------------------------------------- /todo.markdown: -------------------------------------------------------------------------------- 1 | nael todo list 2 | ============== 3 | 4 | * `type` function - Pop's a value from the stack and pushes it's type. **Done** 5 | * tuples - **Done** 6 | * dictionaries 7 | * Loading of C Libraries 8 | * Functions for converting types - **Partly Done** 9 | * `string>int` 10 | * `int>string` 11 | * `float>string` 12 | * etc 13 | * When a module is loaded, it's directory should be added to it's path. 14 | * **FIXED** 15 | * Make every error, report it's origin(function). 16 | * `[0, 0] Error: Wrong types... @ +` - **Partly done** 17 | * Make global variables work, the `def` keyword. 18 | * Make raising exceptions work, and change any functions that need exception raising, to use raise. 19 | * Add stack effects to functions, `func [args] -> [return] (...);` **MAYBE** 20 | * Make var references better, so that a function can change the contents of a variable passed to it. 21 | * generic functions, functions which work only on specified types, like factor's `GENERIC:` 22 | * `func type [args] (...) g;` --------------------------------------------------------------------------------