Git Product home page Git Product logo

miniscript's Introduction

MiniScript

This project contains the source code of both C# and C++ implementations of the MiniScript scripting language.

Sponsor Me!

MiniScript is free, and apart from a small amount of revenue from the books and Unity asset, generates no significant income. Your support is greatly appreciated, and will be used to fund community growth & reward programs like these.

So, click here to sponsor -- contributions of any size are greatly appreciated!

miniscript's People

Contributors

alrado avatar arcnor avatar bananahemic avatar joestrout avatar mantic avatar marcgurevitx avatar matejtavel avatar minerobber9000 avatar olipro avatar rocketorbit avatar sasq64 avatar synapticbytes avatar tsrberry 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

miniscript's Issues

number/string comparisons always return null

Comparisons such as 1 == "" or 42 == [1,2,3] always return null. But other mixed-type comparisons (string/list, list/map, etc.) return 0 (false). Probably that's what comparisons between numbers and other types should also do.

Unknown file "QuickTest.mscp"

Hi there,

First of all, I think it's very cool that you've open sourced MiniScript, it looks really nice, thanks for that!

I've tried running the tests (using Program.cs) and it looks like it's trying to load a file called "QuickTest.mscp" which is nowhere to be found.

Not sure if the file should be there, or that line should be removed, hence this issue.

Thanks!

`not` on empty string returns null instead of 1

Demonstration:

print not []  // OK
print not {}  // OK
print not ""  // wrong

All of these should print 1, but the last one prints null.

Note that when tested as a boolean (e.g. if "" then...), it works correctly (evaluates to false). It appears to be only the not operator that is wrong.

assignment failing with expressions involving `and` and `or`

When assigning an expression whose top-level operator is and or or, in cases where short-circuit evaluation does not apply, it appears the assignment is simply skipped. Demonstration:

foo = 1234
foo = (1 and 0)
print foo

...prints 1234, when it should print 0. A similar failure happens with foo = (0 or 1). If you reverse the order of the operands, the failure does not occur.

A work-around is to add some additional operation to the right-hand side, e.g. foo = (1 and 0) * 1. In this case the top-level operand is *, which works fine.

Issue confirmed in both C# and C++ implementations.

Wrong double value in double.TryParse

If you run the simple miniscript.exe in REPL and write: 0.1, the console output will show you 1 as value.
This is an inconsistent result due to the cultureinfo.
I fixed it putting:

double.TryParse("0.1", NumberStyles.Any, CultureInfo.InvariantCulture, out double value);

list.hasIndex returns true for any string on any nonempty list

Calling hasIndex with any string argument on any nonempty list returns true. Probably this is because under the hood, in the case of a list, we expect a numeric argument, and so convert the argument to a number. So as long as there's an element 0, hasIndex returns true. Example:

lst = [42]
print lst.indexes
print lst.hasIndex("foo")
print lst["foo"]  // throws a KeyException

But actually indexing into a list requires a number; trying to use a string (even one that converts to a valid number) throws a KeyException. So, hasIndex ought to follow the same rules, and return false for any string.

Immutable closures?

I've been testing in the MiniScript command line interpreter and noticed a problem with using closures. They work fine if a value in the outer scope isn't changed by the inner, but attempting to update the value acts as if it's been locally defined in the inner scope rather than outer.

You can currently cheat it by using a complex type (list or map) to get the correct behaviour but this is undesirable.

Examples:

// Non-complex (map/list) closure values are incorrectly shadowed by inner scope?
closure1 = function ()
  count = 0
  return function ()
    count = count + 1
    return count
  end function
end function

c1 = closure1()
print [c1,c1,c1] // prints [1,1,1]

// Using a map or list as a workaround yields expected results.
closure2 = function ()
  count = {"v":0}
  return function ()
    count.v = count.v + 1
    return count.v
  end function
end function

c2 = closure2()
print [c2,c2,c2] // prints [1,2,3]

add support for line continuation

This is important for allowing long strings or other expressions to fit onto a screen or page (particularly important when we start having MiniScript books).

Let's do this in the natural way: if a line ends in something that could obviously not end a line, such as comma or binary operator or open bracket, then allow the statement to continue with the next line.

This should work both in REPL mode (with the standard " more input needed" prompt) and in non-REPL script mode.

add `insert` intrinsic for lists (and strings)

Currently we have no way to insert an element into a list; we can only append to the end (with push). You can sort-of do it by slicing the list into parts, and appending it with a list of the new element, but that's not very efficient and is a lot of work for what is a pretty common need.

While we don't want to go overboard adding new intrinsics or language features, this one meets all the criteria:

  • fills a common need
  • current work-around is awkward and inefficient
  • unlikely to break existing code
  • simple to understand and use

Per MiniScript's usual idiom, we'll define the new intrinsic on strings as well (it makes no sense on numbers or maps).

Can't use "new" with a parameter or computed value

I was trying to make a function that instantiated a class passed in as an argument:

tileAsSprite = function(col, row, baseClass=Sprite)
	noob = new baseClass
	// ...
	return noob
end function

but it doesn't work; if you look at the __isa member of the returned map, it is _0 rather than any useful value.

It looks like maybe the new operator is not fully evaluating its right-hand side before stuffing into __isa.

`replace` intrinsic needs better error-checking

This code sends MiniScript into an infinite loop:

replace("foo", "", "x")

and this code segfaults (at least in command-line MiniScript):

replace("", "a", "b")

Desired behavior: throws an error if the second parameter is empty or null; and when the first parameter (self string) is empty, just returns an empty string.

get command-line MiniScript working on Windows

Command-line MiniScript, written in C++ and defined by main.cpp in this repo, currently works great on Mac and Linux. We need to get it working on Windows too, including with standard command-line editing (i.e., up-arrow to step through previous commands, etc.).

I'm not familiar enough with Windows to efficiently do that. It's probably an easy task for anyone with experience in C++ command-line apps on Windows. Help wanted!

Error Undefined Identifier: 'self' is unknown in this context when in map/class

The following code returns the error: Runtime Error: Undefined Identifier: 'self' is unknown in this context

cmdCalculator = {}
cmdCalculator.one = 1
cmdCalculator.addOneToOne = function()
  return self.one + self.one    <== Error is here
end function

traverseMap = function(map, path, delimiter)
  pathSplitted = path.split(delimiter)
  child = map
  success = true
  for p in pathSplitted
    if child.hasIndex(p) then
      child = child[p]
    else
      success = false
      break
    end if
  end for
  if success then return child
  return null
end function

cmds = {"calculator": cmdCalculator}
traverseMap(cmds, "calculator.addOneToOne", ".")

Bug in ParseAnd function

Value opB = (*this.*nextLevel)(tokens, asLval, statementStart);

The parameters should be "false,false", not inherit from caller.

command-line MiniScript fails when lines in input script are too long

If command-line MiniScript is given a path to a script file that contains very long lines (e.g., 2700 characters long), it terminates without generating any output (other than the standard version header).

Sample script file that shows the problem (invoking as miniscript test.ms, after renaming this file to test.ms of course):

test.ms.txt

Intrinsics unit tests

Is there a reason why Intrinsics don't have unit tests? Or is it only because nobody made them so far? I made some while trying fixes for replace and would add some for all Intrinsics if it's not a problem.

Consider making a script context with isolated intrinsics

I believe it would be useful to have isolated script contexts, each having its own set of intrinsics.
Currently I'm trying to apply miniscript but I have two different contexts on which I want to run scripts and I would like for one context not to share instrinsics with the other.
Another approach would be to have a concept of Global and Local Intrinsics. Math,string, etc are clearly global, user defined could be local.

Using `break` inside if-block results in "strange" error message

I wanted to try out what happens if I use break inside an if:

if 1 then
    break
end if

I get this error:

Compiler Error: unmatched block opener [line 6]

For me as a user it does not make much sense. I would expect something like "You can't use a break inside an if". Or simply for it to be ignored.

I don't see this as a serious error, and it's an edge case, but still surprising.

inconsistent handling of @ in Lvalue context

The @ (address-of) operator does not work properly in a potential lvalue context, i.e., on the first token in a statement. For example:

@rnd

in a REPL should print the function summary for rnd, but instead actually invokes it, returning the value. Slapping parentheses around it, like so:

(@rnd)

works around the problem (but is really not MiniScript's style).

line numbers off after `else` statement

Every else statement causes subsequent line numbers (as reported on error messages) to be off by one. For example, this code has an error on line 10:

if true then
else        
end if
if true then
else        
end if
if true then
else        
end if
x = y = z

...but because it is preceded by three else statements, it reports the error as line 13.

bind local context to local functions as implicit intermediate scope

When defining a function B locally inside a function A, it would often be really helpful for the code in B to have access to the locals of A. This is similar to variable capture in C# and some other languages, and is necessary if, for example, the local function needs to refer to itself. It also turns out to be important to making import work the way it should in environments that support that (such as Mini Micro).

As an example, this code:

globA = "Global A"
funcA = function()
	AlocalA = "Local A in funcA"
	AlocalB = 42
	funcB = function()
		BlocalA = "Local A in funcB"
		print [globA, AlocalA, AlocalB, BlocalA]
	end function
	funcB
end function
funcA

...should print out:
["Global A", "Local A in funcA", 42, "Local A in funcB"]

This issue is somewhat related to #7, but we are talking here not about the calling scope, but rather the scope where the function was defined.

`f isa ...` fails to invoke function f (when used as a statement on the REPL)

Trying to check the result of any function call by entering a statement like f isa number at the REPL prompt does not work, because the function is not invoked. So f isa funcRef will return true, and a check for any other type will return false, for all functions. For example:

rnd isa number

should print 1, but instead prints 0. However this only occurs when used as a statement in this way; if you instead do

print rnd isa number

then it works as expected. Affects both C# and C++ versions of MiniScript.

compiler error: 'else' skips expected 'break'

There seems to be a parsing issue when break appears inside an if block before an else or else if section. The compiler reports Compiler Error: 'else' skips expected 'break', which doesn't make much sense. Sample code (tested both in Try-It! and in command-line MiniScript):

for x in range(1,10)
    if x > 5 then
        print "A"
        break
    else
        print "B"
    end if
end for
print "Done"

Parsing errors in compiler: Missing a matching if-statement

Steps to Reproduce:

  1. Compile the following:
end if

Actual Results: Compilation was successful.

Expected Results: Compiler error, missing a matching if-statement.

Additional information:

  • You can compile more than one "end if" statement in conjunction without error.
  • The issue was observed in the game GreyHack.

Update command-line MiniScript's file module to match Mini Micro

Mini Micro's file API has evolved beyond command-line MiniScript. For one thing, as of v0.6, it's now "file" rather than "File" (to be consistent with our naming convention of capitalizing classes intended to be instantiated, but not capitalizing other maps). And it has more functions.

So, we need to update command-line MiniScript (i.e. main.cpp in this repo) to match the file API defined here.

This would be a good first issue for someone, though talk with me to make sure you understand the updated API in sufficient detail.

Modernizing C# code

This is more of a question/discussion, and I haven't found a better place to put it, so please forgive me if this is not the right one.

I'm not sure what C# version is the code targeting, but there are a few things that can be modernized to make the code a bit less verbose (in the places that matter, I understand that verbosity can be a choice on itself), depending again on the version. The ones I was thinking of were:

  • Replacing string.Format with string interpolation
  • Using object initializers for structs
  • Using expression bodied properties (public string myField => _value instead of public string myField { get { return _value; } })
  • Using var instead of explicit types

This is of course subjective, but IMHO it makes the code less verbose in the right places (well, maybe not var, but the rest certainly do)

Let me know what you think, and I'll open a PR if we agree in some/all of them ๐Ÿ™‚

Incorrect line numbers after "else" statement

Compile:

if true then
    a = 0
else
!    a = 1
end if

Actual: Compiler Error: got Unknown(!) where number, string, or identifier is required [line 5]
Expected: Compiler Error: got Unknown(!) where number, string, or identifier is required [line 4]

Workaround:

if true then
    a = 0
else; // semicolon
!    a = 1
end if

Using `self` results in extra TAC code generation

Text copied from my PR to benchmarks repo, but I was told to report this to issues in this repo too

Currently, when using self, it's getting evaluated as if it was a function, but in practice, we are ALWAYS getting unevaluated result, even when operating on funcRef, unless we are using @self, which is functionally identical but does not emit an extra TAC line for calling it. It would be great if self was always treated by compiler as if it was @self, thus not emitting any extra TAC code

Exactly the same issue applies to globals and locals, and maybe outer, but I haven't checked it

Parsing errors in compiler: Missing an "else if" or "end if" in if-statement

Steps to Reproduce:

  1. Compile the following:
func = function()
  if false then
    return "This is returned"
  if true then
    return "This should be returned"
  end if
  return ""
end function

print(func)

Output:
This is returned

Actual Results: Compilation was successful.

Expected Results: Compiler error, unmatched block opener.

Additional information:

  • The function is necessary for compilation to successfully fail.
  • Try without the function to see expected error.
  • The issue was observed in the game GreyHack.

list.sort crashes when list contains string and null

Miniscript C# Windows console app sha-1 e0f139

script.mini:

list = []
list.push("test")
list.push(null)
list.sort

Console output:

f:\Miniscript>Miniscript.exe script.mini

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at Miniscript.Value.Compare(Value x, Value y)
   at Miniscript.ValueSorter.Compare(Value x, Value y)
   at System.Linq.EnumerableSorter`2.CompareKeys(Int32 index1, Int32 index2)
   at System.Linq.EnumerableSorter`1.QuickSort(Int32[] map, Int32 left, Int32 right)
   at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)
   at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Miniscript.Intrinsics.<>c.<InitIfNeeded>b__2_38(Context context, Result partialResult)
   at Miniscript.Intrinsic.Execute(Int32 id, Context context, Result partialResult)
   at Miniscript.TAC.Line.Evaluate(Context context)
   at Miniscript.TAC.Machine.DoOneLine(Line line, Context context)
   at Miniscript.TAC.Machine.Step()
   at Miniscript.Interpreter.RunUntilDone(Double timeLimit, Boolean returnEarly)
   at MainClass.RunFile(String path, Boolean dumpTAC)
   at MainClass.Main(String[] args)

f:\Miniscript>

Incorrect behavior of `pop`

This code:

d = {1:"one", 2:"two", 3:"three"}
print pop(d)

...prints 1, but I think it should return one instead (unless it's on purpose, of course)? So pop is returning the first key on a map, and not its value.

command-line MiniScript fails on script files containing non-ASCII characters

To reproduce:

  1. Create a text file called test.ms, containing:
print name
  1. Feed this to command-line MiniScript: miniscript test.ms

  2. Observe inappropriate error:

Lexer Error: missing closing quote (") [line 1]

Note that this affects the Try-It! page (which is based on mostly the same code as the command-line version) too.

String replication does not properly terminate the resulting string

This is a little inconsistent, and seems to show up a lot more on some platforms than others. So far it's been demonstrated on Linux (in the C++ command-line build). In fact you can see it on the Try-It! page, with code like this:

x = "Blah"
print len(x*1)
print x*1

Run this code, and very often one or both of the outputs is incorrect. For example, it might say "4" and "BlahU" (wrong text). Or it might even hang.

change scoping rules to skip intermediate call frames

This isn't a bug, per se, but it's a poor design choice: when an identifier can't be found in the local context, then each caller context is checked in turn, until we get to the global scope. This dynamic scoping has occasional uses, but it's more likely than not to lead to hard-to-find bugs. It also makes code analysis more difficult since you can't tell, just by looking at a method, where any nonlocal identifiers might be defined.

So let's change it to a more traditional local/global scoping. In other words, any given identifier will be found in either the local context, or in the global context. Intermediate call contexts will not be checked.

feature request: "import" in command-line MiniScript

Mini Micro has a cool "import" feature described here. (And to close some of the questions open in that thread: yes, you can use if globals == locals to tell when you're running as an import, and you access variables at the module scope using outer.)

I've gotten requests to support import in command-line MiniScript. The C++ version has all the language features (including outer) needed to make this work. It would just need (1) some standard location or environment variable to look for the import modules in, and (2) the code to find the specified module and import it.

command-line MiniScript can't read one file and write another at the same time

If you try this code:

// Create a file to read.
file.writeLines "fileToRead.txt", ["Hey", "Ho", "Hoo-ah"]

// Now, we're going to try opening a file for reading and
// opening a file for writing at the same time.
reader = file.open("fileToRead.txt", "r")
writer = file.open("fileToWrite.txt", "w")

// And let's just copy the lines from one to the other.
while not reader.atEnd
	line = reader.readLine
	print "Copying: " + line
	writer.writeLine line
end while

it fails, actually in an infinite loop of printing "Copying: ". But if you comment out the two lines about writer, it reads the file correctly. It appears that readLine on the input file fails if we also have another file handle open for writing at the same time.

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.