Grape - SC2 programming language

Vestras

Retired
Reaction score
248
Hello,

I would like to introduce you to a new project of mine, a new programming language for StarCraft 2.
Before I start telling you about it, I would like to point out that everything here is subject to change and totally WIP. It is not ensured that this project will ever get released at this point.

I'd like to start out saying that Grape was designed with one goal in mind: No Bullshit™.

What does this mean? It means no trying-to-do-everything-at-once. It means no structs, no backwards compatibility, no properties/accessors, no fancy lambda syntax. Just clean, readable code that is as simple as possible:

Code:
package grape.sample

import system
import system.collections
import system.utils

// mod_base is a built-in class that provides all the base functions for a SC2 mod
// for example, the user can override main which is run on map initialization
class mod inherits mod_base
	string args

    override void main(string args)
        string[100] args_array = args.split(" ")

        if args_array.null_or_empty
            return exit_code_failure
        end

        foreach string arg in args_array
            if arg.null_or_empty
                break
            end

            game.echo(arg)
        end

		// in grape, there are no for loops - I found while loops to be prettier, and more "scripting"-like
		int index = 0
		while index < 10
			game.echo(index)
			index = index + 1
		end

		bool debug_mode_on = false
		bool is_unit_test = false
		switch args_array[0]
			case "debug_mode_on"
				debug_mode_on = true
			end
			case "is_unit_test"
				is_unit_test = true
			end
			default
				// execute default action here
				// this code is run when none of the above cases were handled
			end
		end
    end

	void before_base_initialize()
	end
	
	void after_base_initialize()
	end

	// Constructors/destructors
	ctor mod()
		init this("")
	end

	ctor mod(string args)
		before_base_initialize()
		init base()
		after_base_initialize()

		this.args = args
	end

	dctor mod()
		args.destroy()
	end
end

As you can see, this language is entirely "verbal" and line-based. The syntax is based partially on Ruby and partially on Lua.

There are also some changes from normal language with OOP "logic". For example, all functions inherited from a base class may be overridden. The only case they may not be overridden, is if a function is declared as sealed. This means that there is no virtual modifier.

This language is also very code completion friendly. So while it may take time to write without any "help", it will take almost no time to write in an IDE.

Speaking of IDEs, this language will have full support in Moonlite, and no support in the StarCraft 2 Editor. In Moonlite, however, the features will be along the lines of full code completion like Visual Studio, Quick Links, navigation, etc., etc.

At this point, I am totally open to suggestions for the syntax. However, I will not change the basics -- suggestions for a bracket language should not be posted and will be ignored.

Here's a list of the features I have planned:
  • OOP: only classes, no enums, structs, interfaces -- only classes
  • all declared functions are overridable unless declared as sealed
  • full integration with Moonlite
  • the built-in Galaxy functions will not be usable except through the stdlib provided with the language.
  • "extensions" for the language will be possible, meaning C# code that processes certain items in the written code at compilation-time will be possible. This will open for a lot of things, such as preprocessing certain pieces of code and most likely debugging in Moonlite.

Any feedback will be appreciated.

PS: Yes, Moonlite is still going on. If you're wondering, v1.0.2.0 will feature a lot of new stuff - along with full Andromeda code completion and generally full Andromeda support. It will be released about the same time as Andromeda v2, most likely.
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
I like the idea behind this, it provides a quick debugging language, if I got the jist of it correctly, for testing things when you need something out there and coded without pure efficiency. (I only skimmed it very fast so..)
 

Romek

Super Moderator
Reaction score
963
The 'define' keyword seems a bit pointless.

> no properties/accessors
'Simple' is one thing, but some 'features' are almost necessary for coding. :p

Other than that though, I like it. :thup:
 

Vestras

Retired
Reaction score
248
The 'define' keyword seems a bit pointless.

> no properties/accessors
'Simple' is one thing, but some 'features' are almost necessary for coding. :p

Other than that though, I like it. :thup:

Define can be removed, if necessary.
The reason I'm not planning properties is because I want to add stuff along the way. I might add it in like the second release version or something.

:thup:
 

Moogle

New Member
Reaction score
1
"define var" is icky. You're using C syntax for function arguments (function main(string args)), and then you bring out some weird Visual Basic-like variable syntax for other stuff. Just keep it all C and we'll be much happier.

Code:
class mod inherits mod_base
    override int main(string args)
        string[] args_array = args.split(" ")

        if args_array.empty //Definitely keep that stuff to a minimum
            return exit_code_failure
        end

        foreach arg in args_array //Why declare the var type? The array has the type in it
            if arg.empty
                return exit_code_failure
            end

            game.echo(arg)
        end

        //Kinda prefer ruby's block concept:
        args_array.for_each use arg
              game.echo(arg) //Wouldn't say no to "game.echo arg" either!
        end

        // in a real world scenario, this function wouldn't return int
        // the reason for returning int now is to show how to return types
        return exit_code_success
    end
end


I love the lack of semicolons/required () around ifs.

How is garbage collection going to work? You made a dynamic array.

Can't say much, though I do support a nice language like this as an alternative to Andromeda. Andromeda is nice, but Galaxy is a scripting language and should be written as one.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
Just do what Vexorian did with vJASS then 95% of the people will be happy. Take Galaxy and extend it as far as possible and keep the gimmicky features that don't really fit into the Galaxy syntax at a minimum.

We don't need a language that can't do anything because it won't help anything, nor do we need the shitty mess Andromeda is. We need something in between and that was what vJASS was for WC3.
 

Vestras

Retired
Reaction score
248
"define var" is icky. You're using C syntax for function arguments (function main(string args)), and then you bring out some weird Visual Basic-like variable syntax for other stuff. Just keep it all C and we'll be much happier.

Code:
class mod inherits mod_base
    override int main(string args)
        string[] args_array = args.split(" ")

        if args_array.empty //Definitely keep that stuff to a minimum
            return exit_code_failure
        end

        foreach arg in args_array //Why declare the var type? The array has the type in it
            if arg.empty
                return exit_code_failure
            end

            game.echo(arg)
        end

        //Kinda prefer ruby's block concept:
        args_array.for_each use arg
              game.echo(arg) //Wouldn't say no to "game.echo arg" either!
        end

        // in a real world scenario, this function wouldn't return int
        // the reason for returning int now is to show how to return types
        return exit_code_success
    end
end


I love the lack of semicolons/required () around ifs.

How is garbage collection going to work? You made a dynamic array.

Can't say much, though I do support a nice language like this as an alternative to Andromeda. Andromeda is nice, but Galaxy is a scripting language and should be written as one.

I haven't gotten as far as to garbage collections and stuff like that -- for now I'm only working on the parser.
Due to public demand I have changed the syntax. Check out the new sample in the first post.

Just do what Vexorian did with vJASS then 95% of the people will be happy. Take Galaxy and extend it as far as possible and keep the gimmicky features that don't really fit into the Galaxy syntax at a minimum.

We don't need a language that can't do anything because it won't help anything, nor do we need the shitty mess Andromeda is. We need something in between and that was what vJASS was for WC3.

I fully agree, and it is certainly what I intend to do with this.
 

Vestras

Retired
Reaction score
248
Well of course, it will come without seeds so you can start enjoying Grape the instant you get it!
 

Vestras

Retired
Reaction score
248
Just a little announcement. I'm soon done with the parser - I only need to implement switch statements and the expression parser. Then I can start the code generator.
I also updated the code sample.
 

Vestras

Retired
Reaction score
248
More code samples from the stdlib:

enumerable_base.gp:
Code:
/*
 * Grape stdlib - system package - contains base classes for interop with SC2
 * Copyright (c) 2011 Grape Team. All rights reserved.
 * 
 * The Grape programming language and stdlib are released under the BSD license.
 */

package system.collections

// summary: Represents an enumerable collection of items.
abstract class enumerable_base
	// summary: Gets the enumerator_base of this enumerable_base.
	abstract enumerator_base get_enumerator()
	end
end

enumerator_base.gp:
Code:
/*
 * Grape stdlib - system package - contains base classes for interop with SC2
 * Copyright (c) 2011 Grape Team. All rights reserved.
 * 
 * The Grape programming language and stdlib are released under the BSD license.
 */

package system.collections

// summary: Supports a simple iteration over a generic collection.
abstract class enumerator_base
	// summary: Gets the current item in the enumerator_base.
	abstract object get_current()
	end

	// summary: Moves to the next item in the enumerator_base.
	abstract bool move_next()
	end

	// summary: Resets the enumerator_base.
	abstract void reset()
	end
end

list.gp:
Code:
/*
 * Grape stdlib - system package - contains base classes for interop with SC2
 * Copyright (c) 2011 Grape Team. All rights reserved.
 * 
 * The Grape programming language and stdlib are released under the BSD license.
 */

package system.collections

// summary: Represents a list of objects that can be accessed by index.
class list inherits enumerable_base
	static int maximum_items = 10000
	private static object[maximum_items] empty_array = new object[0]

	private object[maximum_items] items
	private int size
	private int version
	
	class enumerator inherits enumerator_base
		private list l
		private int index
		private int version
		private object current

		override void dispose()
		end

		override bool move_next()
			if version == l.version && index < list.size
				current = l.items[index]
				index = index + 1
				return true
			end

			return move_next_rare()
		end

		private bool move_next_rare()
			if version != l.version
				throw new invalid_operation_exception("Unable to move_next. list.enumerator.version and list.enumerator.version.l.version are not equal.")
			end

			index = l.size + 1
			current = null
			return false
		end

		override object get_current()
			return current
		end

		override void reset()
			if version != l.version
				throw new invalid_operation_exception("Unable to move_next. list.enumerator.version and list.enumerator.version.l.version are not equal.")
			end

			index = 0
			current = null
		end

		internal ctor enumerator(list l)
			this.l = l
			index = 0
			version = l.version
			current = null
		end
	end

	enumerator_base get_enumerator()
		return new list.enumerator(this)
	end

	int get_count()
		return size
	end

	int get_capacity()
		return items.length
	end

	void set_capacity(int value)
		if value != items.length
			if value < size
				throw new argument_out_of_range_exception("value")
			end

			if value > 0
				object[value] dest_array = new object[value]
				if size > 0
					items.copy(0, dest_array, 0, size)
				end

				items = dest_array
			else
				items = empty_array
			end
		end
	end

	void add(object item)
		if size == items.length
			ensure_capacity(size + 1)
		end

		size = size + 1
		items[size] = item
		version = version + 1
	end

	void add_range(enumerable collection)
		insert_range(size, collection)
	end

	void clear()
		if size > 0
			items.clear(0, size)
		end

		version = version + 1
	end

	bool contains(object item)
		if item == null
			int j = 0
			while j < size
				if items[j] == null
					return true
				end

				j = j + 1
			end

			return false
		end

		int i = 0
		while i < size
			if item == items[j]
				return true
			end

			i = i + 1
		end

		return false
	end

	private void ensure_capacity(int min)
		if items.length < min
			int capacity = items.length * 2
			if items.length == 0
				capacity = 4
			end

			if capacity < min
				capacity = min
			end

			set_capacity(capacity)
		end
	end

	int index_of(object item)
		return items.index_of(item, 0, size)
	end

	int index_of(object item, int index)
		if index > size
			throw new argument_out_of_range_exception("index")
		end

		return items.index_of(item, index, size - index)
	end

	int index_of(object item, int index, int count)
		if index > size
			throw new argument_out_of_range_exception("index")
		end

		if count < 0 || index > size - count
			throw new argument_out_of_range_exception("count")
		end

		return items.index_of(item, index, count)
	end

	void insert(int index, object item)
		if index > size
			throw new argument_out_of_range_exception("index")
		end

		if size == items.length
			ensure_capacity(size + 1)
		end

		if index < size
			items.copy(index, items, index + 1, size - index)
		end

		items[index] = item
		size = size + 1
		version = version + 1
	end

	int last_index_of(object item)
		return last_index_of(item, size - 1, size)
	end

	int last_index_of(object item, int index)
		if index >= size
			throw argument_out_of_range_exception("index")
		end

		return last_index_of(item, index, index + 1)
	end

	int last_index_of(object item, int index, int count)
		if size == 0
			return -1
		end

		if index < 0 || index >= size
			throw new argument_out_of_range_exception("index")
		end

		if count < 0 || count > index + 1
			throw new argument_out_of_range_exception("count")
		end

		return items.last_index_of(item, index, count)
	end

	bool remove(object item)
		int index = index_of(item)
		if index >= 0
			remove_at(index)
			return true
		end

		return false
	end

	void remove_at(int index)
		if index >= size
			throw new argument_out_of_range_exception("index")
		end
		
		size = size - 1
		if index < size
			items.copy(index + 1, items, index, size - index)
		end

		items[size] = null
		version = version + 1
	end

	void remove_range(int index, int count)
		if index < 0
			throw new argument_out_of_range_exception("index")
		end

		if size - index < count
			throw new argument_out_of_range_exception("count")
		end

		if count > 0
			size = size - count
			if index < size
				items.copy(index + count, items, index, size - index)
			end

			items.clear(size, count)
			version = version + 1
		end
	end

	ctor list()
		items = empty_array
	end

	ctor list(int capacity)
		if capacity < 0
			throw new argument_out_of_range_exception("capacity")
		end

		items = new object[capacity]
	end
end

object.gp:
Code:
/*
 * Grape stdlib - system package - contains base classes for interop with SC2
 * Copyright (c) 2011 Grape Team. All rights reserved.
 * 
 * The Grape programming language and stdlib are released under the BSD license.
 */

package system

// summary: Represents the base class for all classes in grape - it is the ultimate grape base class.
class object
	// summary: Gets this object's unique hash code.
	int get_hash_code()
		return 0
	end

	// summary: Returns a value indicating whether this object instance is equal to the given object.
	bool equals(object other)
		return other == this
	end

	// summary: Returns a string that represents this object instance.
	string to_string()
		return "object"
	end
end
 

phyrex1an

Staff Member and irregular helper
Reaction score
446
Releasing the stdlib under gpl2 seems like a bad idea. I'm not very knowledgeable about the sc2 map format but if it is anything like the wc3 one then using the stdlib in your map might force you to release the map under gpl2 too. A bsd style license might be better for the standard lib.
 

Vestras

Retired
Reaction score
248
Good suggestion, phyrex1an. Changed, and I updated the samples. The samples now parse correctly and a matching AST is generated.
 

s3rius

Linux is only free if your time is worthless.
Reaction score
130
I do like. Looks better than your first draft. And it's a good thing you decided to keep the return type in the signature where they belong :)

Questshiunns:

Members, by default, are public?

How are you going to handle memory allocation? I see huge chunks of classes being created:
private object[maximum_items] items;
Since it seems array size doesn't have to be constant I guess you're making your own kind of allocation system that draws from huge chunks of normal memory?

On that note I got some random idea. Since memory is very limited in Sc2 and since Grape's Galaxy code will probably eat a nice bit of it once you start using it excessively, maybe it's a nice idea to implement another keyword for variable declaration: table or extern or something like that. These variables would then be created in the Data Table instead of the usual way. It'd be a bit slower (did some benchmarking with data table read/write and it wasn't all that slow) but you could "externalize" larger arrays variables where speed doesn't matter as much.
At least for native Galaxy var types this might be useful.

We get accessors and/or operator overloading? Or is that already part of the gimmicky features mentioned above?
 

Vestras

Retired
Reaction score
248
Yes, members are public by default - kind of the same "pattern" as all members being overridable by default. I wanted everything to be open to the user.

I have not yet come to the planning of code generation and allocation. I will have to look into Galaxy at that time, but I will first create the parser/syntax checker. One thing at a time ;)
I like your extern suggestion, and it is definitely a possible. However, for now I would like to focus on actually having a release and having it work.

We will not get accessors, as they don't fit into the language. Operator overloading is a possibility, but not in the first few versions.
 

Vestras

Retired
Reaction score
248
I would like to announce that the parser is working properly, and that I have the error checker almost ready -- I only have expression error checking left.
I will be getting help on code generation from serius, who is very good with galaxy, so the generated code should be as smooth and optimized as possible. When code generation is done, an alpha release will be ready.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
If you keep that syntax then I must sadly say this: Andromeda > Grape even with all the random stupid shit Andromeda has, it's still better than this.
 

tooltiperror

Super Moderator
Reaction score
231
All of it.

Galaxy is a brace style language, why don't we keep it that way?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top