Giter VIP home page Giter VIP logo

wren's People

Contributors

4d47 avatar avivbeeri avatar ayuusweetfish avatar bjorn avatar bncastle avatar chayimfriedman2 avatar edsrzf avatar freddieridell avatar gsmaverick avatar hachibu avatar iwillspeak avatar jclc avatar kmarekspartz avatar lluchs avatar marcolizza avatar mathewmariani avatar mauvm avatar mhermier avatar minirop avatar munificent avatar nathanielhourt avatar nelarius avatar patriciomacadden avatar purefox48 avatar qard avatar rohansi avatar ruby0x1 avatar sbrl avatar tamc avatar verpeteren avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wren's Issues

No subclassing of builtins?

This:

class SubList is List {}

var ok = new List
ok.add("All works ok")
IO.print(ok)

var problem = new SubList
problem.add("Doesn't work ok")
IO.print(problem)

Gives this:

[All works ok]
[1]    72518 segmentation fault

As of 84ead3b on OSX Yosemite

Is there a problem with subclassing built in classes?

Proposal: Module classes

OK, I've got a strawman proposal for how to handle modules I'd like to get feedback on. Here's my high level goals:

  • (0) The zeroth goal, as always, is do something simple. Wren is tiny and minimal. Tiny tiny tiny.

  • (1) Avoid a monolithic global scope. If two unrelated modules both define the same name at the top level, I'd like that to not cause problems. "Include"-style module systems where importing a module basically means "run it in the top level scope" feel dirty to me. That being said, they do satisfy the zeroth goal.

  • (2) This is more a given than a goal, but the physical location of modules is up to the embedder. In Wren, you'd just specify a "logical" name for a module to import. How that gets mapped to the file system (or whatever) is up to the embedder. Of course, the command-line embedder that comes with Wren will have a, hopefully sane, implemented behavior for this.

  • (3) Handle shared imports. Main imports A and B which both import C. This should only cause C to be loaded and executed once. Since loading a module may have side-effects, I think this is a given.

  • (4) Handle circular imports. This is a bit more contentious in a scripting language. But statically typed languages almost invariably support this and it is handy. In Dart, I have cyclic imports all the time.

    It's more important in a static language because you often need to import a module solely to be able to use its names in type annotations. Wren isn't statically typed, but I intend to write a static analysis tool for it. (Think like dialyzer for Erlang.) So I'd like to not rule out circular imports if possible.

  • (5) Still provide compile errors for undefined globals. Right now, an undefined name is detected and reported at compile time. I really like that. One easy way to handle circular imports is to just implicitly define globals. Then it doesn't matter which order imports are loaded as much as long as you don't use a name until after it's handled.

    But, if possible, I'd like to not do this. I like compile errors for my dumb typos.

  • (6) Let the importer control the name(s) bound from the imported module. The person defining the module doesn't know what other names may be in scope when their module is used. Only the consumer knows that, so ideally the consumer has some control here. This is why, for example, Python has "import as".

Strawman #1 - Lua-style module objects

Lua has a neat system. The require() function loads a script and returns the value returned by that script. You use it like:

local someModule = require('someModule')

So you control the local "prefix" used to access the module, and the module is really just an object. Super simple. In Lua, the object returned is usually a table. Wren isn't table-based, so instead this would naturally be an instance, or possibly a class object. Either works.

We'd probably want a little syntax sugar for imports, but as a first pass, we could start with that. Add a static method that loads a script, evaluates it, and gets the value it returns.

You bind the result of that to a local variable and you're good to go. For example, you could have a 2d vector module like:

class Vector {
  new(x, y) {
    _x = x
    _y = y
  }

  // ...
}

return Vector

I'm not sure what to hang the import method off of so for—just for this doc— I'll just hang it off IO. You could import the above module like:

var Vector = IO.import('vector')

var v = new Vector(1, 2)

For the case where a module exposes a single class, I think this works well. For modules that want to expose constants and other stuff, they'd basically have to hang them off the class as static getters.

Is that gross? Slow?

This trivially addresses goals 0, 1, 2 and has a nice solution for 6. It also addresses 5 with the caveat that you basically lose compile errors for imported "names". Since those become dynamically dispatched getters off the one imported instance, they aren't checked at compile time any more.

For example:

// vector.wren
class Vector {
  static origin { new Vector(0, 0) }
}

return Vector
// main.wren
var Vector = IO.import('vector')

Vector.origon

Here, we mispelled origin, but the compiler won't help. But it still catches other name errors for lexical names you define.

Goal 3, shared imports, is pretty simple. We just keep a table of objects returned by previously loaded modules and return the previous one if the same name is requested. That's what pretty much every language does.

OK, what about circular imports?

Here's a pair of modules that should work:

// main.wren
var Other = IO.import('other')

class Main {
  static inMain {
    IO.print("inMain")
  }
}

Other.inOther

return Main

and:

// other.wren
var Main = IO.import('main')

class Other {
  static inOther {
    IO.print("inOther")
    Main.inMain
  }
}

return Other

What's the execution model here?

  1. Start executing main.wren.
  2. Get to the import and pause main.
  3. Start executing other.wren.
  4. Get to the import

At this point, node can do something nice. It just returns an empty module object for main. When main is resumed, it will imperatively add stuff to that object instead of creating it. So it can just return the shell object. Then, by the time Other.inOther is called, main has added inMain to it and everything is fine.

We can't do that in Wren since classes are created monolithically. We'd have to do something tricky to break the cycle like have import('main') return a proxy, or have the VM track the global it gets assigned to and reassign that after main is done.

Strawman #2 - Module classes

We can't just jack Lua's solution into Wren without some kind of VM changes to handle cycles. Let's try adding a little bit of special sauce.

The basic idea is that each module implicitly gets a class created before the module is loaded. When you import the module, that class is the object that is returned by import().

The clever bit is that we let you add methods to it in the body of the module. I'm thinking something like:

// main.wren
var Other = IO.import('other')

module Main {
  static inMain {
    IO.print("inMain")
  }
}

Other.inOther

and:

// other.wren
var Main = IO.import('main')

module Other {
  static inOther {
    IO.print("inOther")
    Main.inMain
  }
}

The classes have been replaced with module which would be a reserved word meaning "add these methods to the module's implicit class". We could even have the VM implicitly instantiate that class and return the instance so that you don't have to make everything static. Actually, no. Because often you would want your module to be a constructible class.

OK, so now the execution model is:

  1. Start executing main.wren.
  2. That creates a class for the main module and stores it in the module table.
  3. Get to the import('other') and pause main.
  4. Start executing other.wren.
  5. That creates a class for the other module and stores it in the module table.
  6. Get to the import('main'). Main is already in the module table, so just
    return it.
  7. Get to the module definition in other.
  8. Define the inOther method on other's module class.
  9. Finish other.
  10. Resume main.
  11. Get to the module definition in main.
  12. Define the inMain method on main's module class.
  13. Call Other.inOther. It's fine because it's defined now.
  14. That calls Main.inMain. Also fine.

So... I think this works? The two pieces of VM infrastructure (aside from new stuff like the module table and import functionality) we need are:

  1. That ability to pause a script while we execute another.
  2. The ability to add new methods to an existing class.

Fibers already give us #1. And the VM already supports #2 internally. There's just no syntax to allow monkey-patching.

Another option is to actually add a syntax to monkey patch a class. Then, as long as you knew what the name of your module class was, you could just extend it:

// other.wren
var Main = IO.import('main')

extend class Other {
  static inOther {
    IO.print("inOther")
    Main.inMain
  }
}

Even though the importer controls the name of the variable that they bind the module class to, it's still important for the module class itself to have a name since it shows up in stack traces, error messages, etc.

Thoughts?

source highlighting

Because Wren is not official supported on GitHub, why don't give the *.wren-Files an attribute so that *.wren-Files will highlighted? (like Dart, C or something like that)

Example would be like that:

builtin/*.wren -dart
example/* -dart

Proposal: Top level names

(Note: I'm writing this in future tense not to imply that this will happen—it's still a proposal—but just to keep the text simpler.)

OK, here's my proposal for rationalizing the scope issues in #101. The short summary is that we'll borrow Ruby's "constant" syntax.

Right now, Wren has three flavors of identifiers:

  1. Identifiers that start with _ are instance fields.
  2. Identifiers that start with __ are static fields.
  3. Everything else is a "normal" name.

We'll add one more category: identifiers that start with a capital letter are "top level" names. I think the naming rule works pretty naturally for classes, and also for constants if we use an ALL_CAPS convention to name them.

The scoping rules for normal and top level names are different:

Normal names

To resolve a normal identifier that starts with a lowercase name, we start with the current innermost lexical scope. If it's a local variable there, we're done. Otherwise, we walk up the enclosing scopes looking for it in them.

If we hit the edge of a method boundary, we stop there. If it hasn't been found by then, then the name is interpreted to be a getter on this. This is different from the current behavior where a name is only an implicit getter if it isn't defined in any lexically enclosing scope, including ones outside the method. What that means is that this program:

var foo = "top level"

class Bar {
  foo { "instance" }
  method {
    IO.print(foo)
  }
}

(new Bar).method

Will print "instance". This is what Ruby does, and is the behavior I prefer. For what it's worth, Gilad Bracha disagrees (pdf). (In Wren today, this program prints "top level".)

This does mean that you can't access an outer variable with a normal name from inside a method. I think that's an acceptable limitation. Instead of a variable, you can usually just move it to a static field. If this is really a problem, we could add some syntax to express "don't look up this name on this".

Top level names

To resolve a top-level name, we walk the enclosing scopes all the way to the top, ignoring method boundaries. If we find the name in any of those scopes, we're done.

Otherwise, we speculatively assume it will be defined at the top level and resolve it as such. The compiler will keep track of which top-level names have been implicitly declared by this. After it reaches the end of the file, any implicitly declared names that never got a matching definition generate compile errors.

As long as the name gets defined before it's used, everything is fine. This makes mutual recursion like this work:

class Foo {
  bar { new Bar }
}

class Bar {
  foo { new Foo }
}

(new Bar).foo
(new Foo).bar

Since Bar and Foo are both inside methods that aren't called until after Foo and Bar are both defined, no error occurs. If you do try to use a top level variable before it's defined, it's a runtime error:

IO.print(Foo) // runtime error

var Foo = "defined"

Note that "top level" names can still be defined in local scopes and used there:

{
  var TopLevel = "ok"
}

In that case, they go out of scope like any other local variable. We may need a better name for these kinds of identifiers than "top level".

Likewise, you can use capitalized names as getter names:

class Foo {
  Bar { "ok" }
}

It's just that you have to have an explicit receiver to invoke it. Bar will never work call that, but someFoo.Bar is fine. This means this rule still works if we use class objects as modules that want to contain multiple classes (#79).

I've mulled this over for a while, and I'm pretty happy with it. The fact that it lines up with Ruby gives me some confidence that it's usable in practice, even in large programs.

Thoughts?

Proposal: Header files / header of a file for Modules

A wrenh file could give the structure of the corresponding wren file:

module Collections {
   class Set {
     new(sequence)
     union(other)
     // ...
   }
}

This could also go at the top of a file, with something like a where after the last closing bracket a la Haskell.

I believe this could satisfy #79's goals of 1, 2, 3, 4 (with two passes, loading the header declarations first and then the definitions), 5, 6, and possibly 0 and 7.

Cannot define some custom operators

I cannot create custom operators for | and &:

class Set {
  // ...
  | that {
    // Union
    return new Set  // ...
  }
}

I may add tests for each of the possible custom operators.

File IO

I'd like to be able to read and write files.

Performance optimisation of readName method

Hi.

I just started to also read a bit of what is happening under the hood of wren and started with the compiler. It looks really straight forward. Kind of what I write when writing simple compilers. While reading, I stumbled upon line 529ff and thought that it could be done faster, right?

My C knowledge is a bit rusty (did that in school a bit, but that's really long ago), but the readName method seems to be doing much more then it has to. If you would read a break token, you would still call the ìsKeyword-method 15 times more often then you would have, when bailing out after matching the break if statement. Maybe something like:

if (isKeyword(parser, "break")) type = TOKEN_BREAK;
else if (isKeyword(parser, "class")) type = TOKEN_CLASS;
else if (isKeyword(parser, "else")) type = TOKEN_ELSE;
…

Or using a switch statement or something?

Wren does not compile in 32bits (mingw/windows)

I tried to compile Wren using mingw or cygwin in 32b, and it failed miserably.

src/wren_value.h:511:24: error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast]
 #define AS_OBJ(value) ((Obj*)((value).bits & ~(SIGN_BIT | QNAN)))

Then I tried adding -m64 and it compiled on cygwin (mingw can't because of stdarg.h) but "core dumped" on execution of hello.wren (edit: running wren.exe without any argument give the same results).

Program received signal SIGSEGV, Segmentation fault.
0x0000000100402a1b in wrenUnpinObj ()
(gdb) bt

0 0x0000000100402a1b in wrenUnpinObj ()

1 0x0000000100404ab6 in defineClass ()

2 0x000000010040626c in wrenInitializeCore ()

3 0x00000001004025fa in wrenNewVM ()

4 0x000000010040b42a in main ()

Why a stack machine instead of a register machine?

Why is Wren's VM a stack machine and not a register machine? The tradeoffs are pretty well known: stack VMs are simpler and usually generate smaller bytecode, while register VMs are a little tougher to generate code for and generate fewer instructions (making them faster in general).

Lua provides a good reference for code generation and interpretation for a register VM.

Infinite recursion of lists containing themselves

Consider the following Wren code:

var x = []
x.add(x)
IO.print(x)

This results in a stack overflow. The reason is because the List.toString method calls this[i].toString which then recurses as x contains a reference of itself. Is this desirable? I tried the equivalent code in Python and received the following:

[[...]]

Let me know of your thoughts on this. I feel it would be too much hassle to implement something like what Python does. However there does need to be a more graceful error message other than a crash.

Coding style

You are a book author, yet your coding style is lacking at least about new lines after each and every one line if.

For example:
if (argc == 1) return runRepl(vm);

It looks as if you trying to save lines of code.
No offence, this is a bad habbit which produces nothing. Not even readability, on the contrary.

:)

Odd newline and parenthesization rules

This works:

class Set {
  // ...

  - that {
    // Set minus
    return new Set(
      _list.where(
        fn (element) {
          return (
              !that.contains(element))
        }))
  }
}

But when I reformat to make the parens at the end look "better", this fails:

class Set {
  // ...

  - that {
    // Set minus
    return new Set(
      _list.where(
        fn (element) {
          return (
              !that.contains(element)
          )
        }
      )
    )
  }
}

Okay, that isn't that much better, but I don't think it should fail.

Error message:

[example/set.wren line 10] Error on newline: Expect ')' after expression.
[example/set.wren line 11] Error on ')': Expect '}' after block body.
[example/set.wren line 11] Error on newline: Expect ')' after arguments.
[example/set.wren line 12] Error on '}': Expect ')' after arguments.
[example/set.wren line 13] Error on ')': Unexpected token for expression.
[example/set.wren line 14] Error on ')': Unexpected token for expression.

Optional stdlib / cookbook

I know the point is to keep Wren core small, but perhaps an opt-in standard library with broader features would be useful. This could be implemented as more of a cookbook, including only the needed portions when embedding.

UTF8 support?

One of my biggest pet peeves with lua is the lack of native unicode support. It is my opinion that there is no reason for a new programming language to still be stuck in ASCII-land. I would be willing to investigate different ways of making wren strings be utf8 by default, instead of ASCII, if it is something you think would be worth putting the time into.

Question: Different Function Syntax for Methods and Anonymous Blocks

I just have a few questions about the Function syntax in Wren and the reasoning behind it. I am on Windows so I couldn't get it running to find out for myself. I have never written a language so I have no idea what the internal trade offs may be, or why certain syntax variation may actually make the language simpler or faster.

For a method the arguments list is wrapped in parens, followed by the block braces.

callMe(fn) {
  // Call it...
}

An anonymous function, however, has its arguments surrounded by pipes.

{ |first, last|
  IO.print("Hi, " + first + " " + last + "!")
}

You can then pass this function immediately to another function as an argument.

callMe { |first, last|
  IO.print("Hi, " + first + " " + last + "!")
}

But if you want to pass multiple arguments to a function, including a function, you use a different syntax again. Wrapping the non function arguments in parens and leaving the anonymous function bare.

blondie.callMeAt(867, 5309) {
  IO.print("This is the body!")
}

When passing an anonymous function as a parameter to another function, it doesn't need to be instantiated. But when storing a function in a variable, it does.

var someFn = new Fn {
  IO.print("Hi!")
}

It seems to me the syntax could have a smaller footprint as follows.

Stroring anonymous function without new Fn, just like passing the function to a method.

var someFn = {
  IO.print("Hi!")
}

Calling a method with a function(s) as an argument, just like any other argument.

callMe({
  IO.print('First function!')
},{
  IO.print('Second function!')
},3,4,5)

An anonymous function's arguments stored in parens instead of pipes, just like methods.


(first, last) {
  IO.print("Hi, " + first + " " + last + "!")
}

Looking forward to trying Wren out, and hearing your thoughts.

Ctrl-D to exit repl

Ctrl-D causes an infinite loop in the repl:

$ wren
> ^D [(repl) line 1] Error on 'P': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Expect end of file.
[(repl) line 1] Error on 'P': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Expect end of file.
[(repl) line 1] Error on 'P': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Expect end of file.
[(repl) line 1] Error on 'P': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Expect end of file.
[(repl) line 1] Error on 'P': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
[(repl) line 1] Error on 'h': Undefined variable.
[(repl) line 1] Error: Invalid character ''.
....
^C
$

SegFault when overridding toString

Hello.

Just getting warm with the wren language, and I must say: It's neat. I just do useless playaround stuff with it, and came across the following problem:

When you try running the following "program" you will get a Segmentation Fault: 11.

class My {
  toString { 3 }
}

var my = new My

IO.print(my)

Of course the problem is, that I return a non-string in the toString method, if I would write { "3"} then all would work as expected. Still, I think there should be a warning or error raised, and not abort with a SegFault.

What do you think?

Parse of "i-1" is space-sensitive

The parse of i - 1 varies depending on whether or not there's a space before the 1. If there's no space, it's interpreted as "negative one" and causes confusing parse failures:

var i = 1
// These would parse as i (-1) and give the error: Error on '-1': Expect ')' after arguments.
//IO.print(i-1)
//IO.print(i -1)

// These are fine
IO.print(i - 1)
IO.print(i- 1)

I can accept that there might be one preferred way to write it, but this doesn't seem correct.

Seperate building from Xcode

I can build it and run it within Xcode, but I can't seem to run it outside of Xcode. I'd like to try it out without firing up Xcode. How dependent is wren on Xcode?

Multiple problems prevent building with MSVC

You mentioned that it would be nice to have project files for MSVS to go with the existing Xcode project and makefiles, so I tried to make one, but the problem is that the code is too MSVC-unfriendly to have a realistic chance of compiling it in the current state.

Just in case you're interested in fixing this, here are the problems I found:

  1. C99 is a problem on its own. It's not supported (and never will be) by Microsoft C front-end, so the only chance of building this code is by compiling it with the C++ compiler. This, in turn, means that a lot of casts that are implicit in C (e.g. void* result of malloc() to char*) need to be made explicit which would be already pretty ugly, probably, so this looks like a show stopper all on its own.
  2. But the code also uses some gcc extensions which are not even C99 (and hence not supported by even the C++ compiler): unless I totally forgot my C (which is possible to be honest...), WrenConfiguration struct initializer in main.c is not in C99. And neither are computed gotos used extensively in wren_vm.c, and while they can already be disabled by a config option, this really should be done automatically when using a non-gcc compiler (testing for gcc would cover clang too).
  3. getline() is also POSIX and not C99 and is not available with MSVC.

There are probably many more problems but the above was enough to stop me.

cleanup suggestions

Replied on reddit, but maybe better here:

Your git repo is already very clean, so some more cleanup suggestions (note I am not a C programmer):

  • Maybe add a README.md file to your repo root so that the project looks pretty on github. Your markdown files could maybe be more github markdown compatible (^title and snippet language, easier to read/review them on github).
  • It would look nice if you included continuous integration like travis, it is free and quick to set up.
  • The 'metrics' script does not need to be at project root, and can be named metrics.py.
  • The wren executable should go somewhere into the build folder.
  • A modern alternative to makefiles could be nice (cmake, autotools, scons) for portability.
  • Could be nice to doxygen your source-code.

Building wren I got make errors (Linux cc 4.8.1):

src/main.c:84:5: error: ignoring return value of ‘getline’, declared with attribute warn_unused_result

hacked that away, then could not link to math.h functions:

error: undefined reference to fmin()

had to move '-lm' to end of Makefile line.

You might be interested in the cpplint tool as well, a 2012 version is available via pypi. Maybe run it with: $ cpplint --filter=-whitespace,-readability/casting,-legal,-readability src/.c src/.h include/*.h

Single-pass compiler questions

As per the documentation, the parsing and code emission is done in a single pass, but is tokenization included in that pass as well, or does code get fully tokenized and then a single pass is conducted over the tokens? If the latter is the case, might including the tokenization in the single pass be another optimization?

Secondly, is the single pass conducted via an attribute grammar?

Build fails under GCC on Linux x86_64

~/code/wren $ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

~/code/wren $ make
cc -c -std=c99 -Wall -Werror -Os -Iinclude -o build/release/main.o src/main.c
src/main.c: In function ‘runRepl’:
src/main.c:84:12: error: ignoring return value of ‘getline’, declared with attribute warn_unused_result [-Werror=unused-result]
     getline(&line, &size, stdin);
            ^
cc1: all warnings being treated as errors
make: *** [build/release/main.o] Error 1

Reconsider having both .. and ... for ranges?

The .. and ... operators seem confusingly similar. I know Ruby does this, but is it a good idea to copy it?

But I'm not sure what to suggest. Perhaps 1 ..< 5 for a half-open range? Or perhaps having both isn't actually necessary?

(This is just a bit of ignorable bikeshedding. Otherwise, I find Wren rather tasteful.)

Proposal: is -> isA

The current is can become isA and then is can check if two objects are the same (like in Python).

Or, add in === (like in JavaScript).

I'm attempting to fix the printing lists with circular references problem, but can't seem to find a way to check if objects are the same object, just if they are equal, or just what their class is.

Classes cannot create new instances of themselves internally

class Set {
  // ...
  map (f) {
    var newSet = new Set() // Errors on this line.
    // for (element in this) newSet.add(f.call(element))
    return newSet
  }
}

I'm working on a Set implementation, and I cannot create a new instance of Set to put elements into:

[example/set.wren line 4] Error on ')': Unexpected token for expression.
[example/set.wren line 4] Error on newline: Expect ')' after arguments.
[example/set.wren line 6] Error on 'return': Expect '}' after block body.
[example/set.wren line 6] Error on 'newSet': Expect newline after definition in class.
[example/set.wren line 6] Error on newline: Expect method definition.
[example/set.wren line 7] Error on '}': Expect end of file.

In List, I just used [], so this wasn't an issue.

Number test failures

When I run make test, I get a test failure:

$ make test
FAIL: test/number/divide.wren     
      Expected output "nan" on line 7 and got "-nan".
      Expected output "nan" on line 8 and got "-nan".
      Expected output "nan" on line 11 and got "-nan".
      Expected output "nan" on line 12 and got "-nan".

The relevant test cases:

IO.print(0 / 0)         // expect: nan
IO.print(-0 / 0)        // expect: nan
IO.print(0 / -0)        // expect: nan
IO.print(-0 / -0)       // expect: nan

Lua 5.2.3 gives the same -nan results, for what it's worth.

Maps

The documentation mentions that maps are a TODO, and even without that they'd seem like a glaring omission. I'm sure @munificent has some thoughts and maybe even some code written, but if it's not too far along we can gather thoughts here.

Syntax

The expected syntax would be something like:

map = {
    "foo": 1,
    "bar": 2,
}

However, braces are already used in a couple places, which might make this tough to parse. (Then again, it might be fine. I haven't thought about it too hard.)

I can't think of a precedent for other syntaxes, but there probably are some.

Hash function

I expect that objects will start having a hash method that determines identity and how they're placed in a map.

A few questions that need answering:

  • What hash function will we use for strings?
    • SipHash is commonly used. However, its 64-bit-ness might be bad if we're targeting embedded systems, which are predominantly 32-bit. (I think it's pretty fair to assume most other types of machines are 64-bit at this point.)
    • LuaJIT does something that doesn't look like it even takes the whole string into consideration.
    • Lua does something very simple (not sure if there's a name for it):
for (l1=l; l1>=step; l1-=step)  /* compute hash */
    h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1]));
  • Do we need to worry about hash-flooding DoS attacks, or is that not a use case we care about? (Or do we simply punt the issue?)
    • My vote is to not worry about it/punt. Lua doesn't seem to care and Wren occupies a similar space.

Map implementation

We can start with a basic hash table implementation and go from there, but if there's a good one we can copy from somewhere it's worth considering.

Toward first class functions

Just playing around:

class MapFunc {
  apply(f, list) {
    var newList = []
    if (list.count > 0) {
      for (element in list) {
        newList.add(f.apply(element))
      }
    }
    return newList }
}

var map = new MapFunc

class IncFunc { apply(a) { return a + 1 }; }
var inc = new IncFunc

map.apply(inc, [1, 2, 3])
IO.print(map.apply(inc, [1, 2, 3])) // expect: [2, 3, 4]

Thoughts on this approach?

Implement a Wren parser in Wren

This wouldn't necessarily be for a single-pass bytecode emitter like the existing parser. I'm not suggesting replacing it by bootstrapping Wren. Instead, I'm suggesting a parser to construct ASTs. This could be useful for iterating on the grammar, and a few other projects I'd like to tackle, to be mentioned in subsequent issues.

Bad scoping behavior

Wren has two existing problems related to scope that really need to be fixed. They are, in my mind, the biggest "broken" part of Wren and my highest priority to address.

The first is that it doesn't understand mutual recursion at the top level. This program doesn't work (where I'll define "doesn't work" in a second):

class Foo {
  bar { new Bar }
}

class Bar {
  foo { new Foo }
}

The second is that, within a method, any unknown identifier is presumed to be a getter on this. So in this program:

class Foo {
  bar { someRandomName }
}

It treats someRandomName as equivalent to this.someRandomName. So by doesn't work, what I mean is that the first program is actually interpreted like:

class Foo {
  bar { new this.Bar } // Bar isn't defined yet so becomes self send
}

class Bar {
  foo { new Foo }      // Foo is now, so is lexical
}

I think we can all agree that's pretty busted. The fact that a lexical variable shadows a getter on this also has some other weird consequences. Take this program:

class Person {
  name { "Fred" }
  greet {
    IO.print("Hi, my name is", name)
  }
}

(new Person).greet

Right now, it works just fine. But if you change it to:

var name = "WTF" // <-- Add this.

class Person {
  name { "Fred" }
  greet {
    IO.print("Hi, my name is", name)
  }
}

(new Person).greet

Now, it prints Hi, my name is WTF, which I don't think is what users intuit. (Though, for what it's worth, Dart does work this way.)

The obvious solution is that when you see an identifier in a class, you only look for locals in the method. If it isn't defined there, you can consider it a getter on this and don't look outside of the class. Alas, that doesn't work:

class Foo {
  method {
    IO.print("The String class is", String)
  }
}

(new Foo).method

Here, we'd compile String as this.String, which is clearly not right!

Distilled down, the two problems are:

  1. How do we handle top-level variables defined after their use?
  2. How do we decide if an identifier in a method is a variable outside of the class or a getter on this?

I'll leave this bug for the problem, and we can use separate issues for proposals to address these. (I'm filing both of these problems as a single bug because they're intertwined such that any solution will have to solve both.)

wrenfmt

After #87 and #88, it would be nice to have a wrenfmt program which would standardize syntax/indentation. This could also allow for migration to newer syntaxes, which could lower the barrier to entry for such a young language.

Considering add thread support?

Is it too early to consider something like this? I am interseted to contribute to wren myself and this is the first thing come out off my head. I would love to see that there is a scripting language that does not have a GIL since CPython and Node.js are the ones I use most often but their performance is limited by their GIL somehow.

Switching to a more permissive licensing, even

Quoting: "Wren uses the OSI-approved MIT license. I'm not sure exactly what that means, but I went with the most permissive license I could find".

Even if im not a expert in the matter, ZLIB/LibPNG license or Boost Software License are one step ahead in terms of permissiveness: attribution is optional in binary distributions; whereas in MIT license is always mandatory.

So if you want to go to the most permissive license you could go the ZLIB/LibPNG route (which is very gamedev popular nowadays), BOOST route (which is far less popular) or even Unlicense/Public Domain (which are controversial "licenses" in many countries around the world, afaik).

Just my two cents : )
Thanks for wren & the blog/book.
Keep up the good work

  • r-lyeh

PS: if you want to keep going on the MIT route, you could just add an explicit clause on the MIT license (as seen here) which would makes the license closer to the zlib or boost contexts. Even if looks like an attractive option, I think this new license would need approval from diverse opensource non-profit organizations (FSF, OSI, Copyfree Initiative, Debian, Fedora, etc) before any integration of Wren or derivatives into their products. But then again, I am no license expert in any way.

Range class should validate input parameters

Consider the following piece of Wren code:

for (i in 7..6) {
  IO.print(i)
}

It will loop indefinitely (or until i overflows and eventually equals to 6). This is of course undesirable. A simple fix would be to validate that _min <= _max. However this is just a part of a larger problem. Consider the following Wren code:

for (i in 0..0.1) {
  IO.print(i)
}

This will loop forever because the iterate method in the Range class increments by 1. I don't think decimal numbers should be allowed in dotted sequence expressions, because what does 0..0.1 really even represent? But how do you avoid decimals here, as Wren treats everything as a double beneath the hood and both 0 and 0.1 are instances of Num?

wrenpp / macros

Compile-time macros! Another use of #88.

This could be used to implement #46 / #79. Mixins could also be implemented with macros (copying definitions down into mixed-into classes).

Code sample on website / README doesn't actually compile?

The code sample at the top of the README doesn't actually compile, should it? Seems like it would need to be changed to this:

IO.print("Hello, world!")

class Wren {
  adjectives { ["small", "clean", "fast"] }
  languageType { "scripting" }
}

Dangling else

This: if (true) if (false) return 1 else return 2

Could be:

if (true) {
  if (false) {
    return 1
  }
} else {
  return 2
}

Or:

if (true) {
  if (false) {
    return 1
  } else {
    return 2
  }
}

Do the C rules apply? i.e. associating the else clause with the nearest if? This should be documented.

Feature: import/include/require code?

Did you plan on including some mechanism to import/include/require code in Wren? Or is all code outside of core/iosupposed to be provided by the host application?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.