I've often wanted a language which has all the benefits of Python, Bash, etc. smushed together in one heap. Here's a few notes about something along those lines. # Goals * Natural syntax * Documentation and code are close and tight * Easy to parse and write interpreter for * Syntax is simple * Decent built-in datatypes * Easy access to Python and Bash stuff That's quite a harsh requirement list. # Datatypes * Numbers e.g. 5.3, 87, 0.0005, -7, 2i @@ What about commas? What about exponents? * Filenames/URIs e.g. , @@ Is c:/text.txt a URI or filename? How do you know? * Command line flags e.g. --help, -h @@ Makes whitespace important * Strings - special arrays of unicode characters, essentially e.g. "Hello!", "What\U+203D" @@ Interpolation? * Hashtables, Arrays... e.g. @@ * Regular expressions e.g. @@ # Syntax Evaluate { ... } blocks. Bit like lisp? def something "hello" def myfunction { "return value"! } if { equals blargh "hello" } { something! } How to call functions? Just say the name. if { equals blargh "hello" } something Perhaps coordinate, so instead of: equals blargh "hello" if the first object is not callable, you check the second and so on: blargh equals "hello" plus allow some special aliases there, and... blargh == "hello" could use = for def/set then too. These would be equivalent: def something { ... } something = { ... } So if just saying a function calls it, how to quote it? something = { ... } say/print/echo { quote something } perhaps like that. Or using an operator for quote... say { 'something } Looks good. So, how about a simple hello world program... say "Hello world!" And a more complex one... def main(): if sys.argv[1] == '--planet': print "Hello planet!" else: print "Hello world!" if __name__ == '__main__': main() main = { if --planet { say "Hello planet!" } else { say "Hello world!" } } if { name == "main" } main Or, better than that, if { name == "main" } could become... ifnamemain main Or some better function name. It seems natural instead of saying: if --planet { say "Hello planet!" } to say: if --planet say "Hello planet!" I suppose if can have that sort of thing built in? An { ... } block is a datatype really. It's a chunk of code. Technically I guess it's an anonymous function, a lambda-a-like. How do we do for loops? How do we pass args? def add(p, q): return p + q add = { args p q p + q! } or better yet... add = { @ p q; p + q! } As for for loops, for can obviously be a command... for line in open('test.txt'): print line for { @ line out line } or even something like: for { out { arg } } out, which is like sys.stdout.write, could even be made to call args it gets that are callable, so it'd become: for { out arg } Of course, it could be made to do a similar thing to files... out Perhaps coordination should be done only if there are > 2 args. That way you won't be able to do something like: out But you will be able to do: "Hello" out " world!" "\n" Which is a bit wacky, but oh well. When a function block is made, it should have a set of args, which you can get with either arg for a single arg, args for an array of all, and @ as an alias of args, which can take args itself that assign to global variables. You should also be able to get a link to the parent block. So for example: def hello(a, b, c): print a, b, c def world(p, q, r): print a, b, c print p, q, r hello = { @ a b c say a b c # perhaps print should do the automatic " " adding... # or, comma can be set to space! , = " " world = { @ p q r [a, b, c] = { args parent } } } Of course, any child function/block inherits the variables from its parent anyway so that's pretty unneccesary. [a, b, c] for lists is kinda nice. [a, b, c] will be a list with unfulfilled global variables in it. When you try to set a list with unfulfilled global variables in it to a list of the same length, it assigns the parts. Let's call unfulfilled ones blank variables. And global variables are just variables. What happens when you try to get the value of a blank variable? Issue: something = { args parent } [p, q, r] = { args parent } The latter is expecting a call, the former is assigning to a block. Clearly for function definition we need to make sure that blocks can be quoted. So what is an instantly called block going to look like? Use ( ) perhaps, a la evaluation? I kinda wanted to use that for "set". [p, q, r] = ( args parent ) Or perhaps a postfix for execing, like ! for returning: [p, q, r] = { args parent }* Could even make it a postfix operator [p, q, r] = !{ args parent } Though there it looks like "not". Full stop might work: [p, q, r] = { args parent }. But it's a bit non-obvious. There's always "call"... [p, q, r] = { call { args parent } } but... call needs itself to be called! Therefore this has to be inherent. I'd actually kinda like to use [ ] for functions, ( ) for arrays, and { } for calls then, but I dunno. I just want to avoid it looking like lisp. def something(p, q): if p == q: print "p equaled q" else: print "p did not equal q" something = [ @ p q if { p == q } { say "p equalled q" } else { say "p did not equal q" } ] Ooh, or double {{ }} for quoted blocks? something = {{ @ p q if { p == q } { say "p equalled q" } else { say "p did not equal q" } }} And = (i.e. def) could be magic when it gets an array and a quoted function... something = (p q) {{ if { p == q } { say "p equalled q" } else { say "p did not equal q" } }} That's looking nice. What if [ ] was list, ( ) was eval, and { } was qfunc? something = [p q] { if (p == q) (say "p equalled q") else (say "p did not equal q!) } I guess that is kinda cool. Oh, though actually, here the says need to be in quoted blocks because we don't want them to eval unless the conditionals are true, and it's the if and else that eval them! This is also true for the = example, except that = doesn't really know whether to call the function or not. I suppose that's where quote comes in? Yeah. Quote doesn't do anything special, it's up to the functions to decide how to handle it. So... something = (p q) { if {p == q} {say "p equalled q"} else {say "p did not equal q"} child = { (p q) = { parent args } } } Hmm... tricky to know what = should do with its args. These function/blocks seem to be basically closures. Will we ever need to run a block just by itself? call or run could do that. run is good. # Variable Space and Search Paths There will be a program variable space and a user variable space. The user will be able to change the values in both spaces. Unprefixed variables will be looked up in both spaced, with a precedence towards the user space, unless some particular program variable space variable, e.g. @precedence, is changed. To access only user space ones, unambiguously, prefix with $. To access only program space, prefix with something like @, or maybe & or %. There's also the question of bash programs, on $PATH, and how to handle them. It seems like a good idea to treat them as predefined closures, and to have a variable space for them too, e.g. using %. So: %cat "example.txt" Will be like cat example.txt in bash. Obviously since we're doing lazy evaluation, we'll want continuations, and the return values will be strings in a constant continuation. One problem with this is that pipes have two output streams, generally, stdout and stderr. It's not clear how one would get something from stderr. for { %cat "example.txt" } { say arg } Is basically a cat wrapper. Oh, it could return: for { %cat "example.txt" } { @ fd line if {fd == stdout} { say line } } Which is kinda suck. And anyway, there needs to be an ! in there to return. for { %cat "example.txt"! } { if {@1 == stdout} {say @2} } There should be an sh function, too: for { sh cat "example.txt"! } { if {@1 == stdout} {say @2} } Note how @1 and $1 can exist, but can't be looked up without the prefix. sh = { @ arg * args command = { blankVariableToCommand arg! } command { expand args } } or... er... something like that. It gets rather tricky at that level. It's mainly the expand args thing which is tricky. I don't think it works--I think it, rather, needs to be: sh = (arg * args) { command = { blankVariableToCommand arg! } exec command args } Which is quite nice. In fact, it should allow sh %cat too, so... sh = (arg * args) { if {{type arg} == BlankVariable} { command = { blankVariableToCommand arg } } exec command args } Interesting points: note that we want command to propagate up there, so actually that's procedural. And it's not clear with: { ... } == { ... } Whether the blocks should be called or not. I think they should. The non-call version of that is essentially "is". { ... } is { ... } Of course, is won't call blocks passed to it. # Fuller Example A fuller example may now be possible. Let's try drafting http://inamidst.com/code/repres.py in this language. doc " repres.prog - Use Python String Representation Author: Sean B. Palmer, inamidst.com " repres = (input options out) { if {out == null} { out = stdout } if {-m || --multiline} { write out { py repr { { join input } + "\n" } } } else { writelines out { list { py repr { arg + "\n" } } input }} } main = (argv) { args => { argv from 1! } if {not args} { input = stdin } else { input => { fileinput args! } } try { repres input } except (IOError) { pass } } Quite a few things to note therein already. Dorky syntax, and the use of a new operator for setting without quoting closures. To take the latter first, => was a simple idea for setting to the value of the callee. It could even be parsed something like: input = > { ... }, i.e. def input > { ... }. As for the dorky syntax, these two lines stand out: write out { py repr { { join input } + "\n" } } list { py repr { arg + "\n" } } input The list function is a bit like Python's list comprehensions. What if we had our own representation function? write out { pretty { { join input! } + "\n" }! } list { pretty { arg + "\n"! }! } input I just noticed the lack of ! too. I think we need to do something like autoreturn in perl. write out { pretty { { join input } + "\n" } } list { pretty { arg + "" } } input Okay, so... the problem is this nesting. Obviously, when you have longer things it's not much of a problem. So though you can't do: "something" + "blargh" + "whatever" (Actually, + could ignore itself and concat all both sides, but still), you can do: concat "something" "blargh" "whatever" But actually, the ignoring self idea is good: plus = (* args) { total = 0 for args { py add total { arg } } total! } Uh, I guess that'd be like defining + in Python. I do rather like !, though I guess the point of it is to do nothing... unless! It could be useful for returning something explicitly and then doing other stuff later. That is often very useful when you want to close a file. def read(filename): f = open(filename) bytes = f.read() f.close() return bytes Ignoring the URI type, that'd be something like: read = (filename) { f = { open filename } read f! close f } Or f close if we allow coordination there. What ! can do is set the closure's returnvalue to that value, then when it exits, that overrides. The variable name could be "result". Hmm, we need a space for closure variables too then, unless we just use builtins all the time: read = (filename) { f = { open filename } setBlockVariable result { read f } close f } setBlockVariable could have -> as an alias or something. On the other hand I can't imagine one would want to do it often. One problem with postfix ! is that it means that every command would have to understand it. So there would have to be a preprocessor for commands... That could be pretty simple, and even overridden by the user. So its spec could be something like: preprocessor = (* args) { if {{last args} == !} { setBlockVariable result { (setBlockVariable result) + { args to -1 } } } else { setBlockVariable result { args } } } Obviously we could use ! syntax in there, but that'd make it all so, so meta. Actually, having return be a synonym for setBlockVariable result would be a good idea: @preprocessor = (* args) { if {{last args} == !} { return { (return) + {args to -1} } } else { return { args } } } It might be nice to chain preprocessors, too, so that would be that there would need to be a builtin to get the original value of something... Though I suppose that @preprocessor in its own definition would equal the old value. -- Sean B. Palmer, inamidst.com