Just something fun:
local res = Compiler.compile([===[
package testing;
import a.b.c;
public class Banana {
public static Banana test = new Banana(1);
public String main(String[] test) {
print("hi");
}
}
]===])
print("res:",res)
print_table(res,"","res")
res: table: 7C7AD0B0
- classes
- testing.Banana
- fields
- 1
- type = "Banana"
- name = "test"
- static = true
- default
- 1 = "callConstructor"
- 2 = "Banana"
- 3
- 1
- 1 = "constant"
- 2 = 1
- access = 0
- access = 0
- path = "testing.Banana"
- name = "Banana"
- methods
- 1
- arguments
- 1
- 1 = "String[]"
- 2 = "test"
- name = "main"
- access = 0
- static = false
- result = "String"
- body
- 1
- 1 = "callFunction"
- 2 = "print"
- 3
- 1
- 1 = "constant"
- 2 = "hi"
- importedClasses
- 1 = "a.b.c"
- package = "testing"
- natives
omg you are actually doing it.
If this ever becomes a place put Glue70 - Casin in it please
Also if you are finished that, if you want I could edit it around a bit and really give it a vaporwave feel to it. And no I dont mean sticking windows 98 machines around the place
Nice! What kind of parser did you build for it?
So far my code can parse and execute expressions along the lines of “1 * (4 * 2 + 3) + -4 / (2 + (1.5 * 8) / 2)”. I’m adding variables to it now, developing the parser and VM in unison but as separate modules.
I just started a bit coding, thinking about some article you gave me the link to sometime ago.
That link was mostly for parsing Lua, but I’m using some knowledge I got from it to parse this “Java”.
It’s going surprisingly easy, although I haven’t done the difficult stuff like local variables yet.
Currently busy with implementing a VM that can execute the stuff it already parses.
Parser
VM
So it’s actually organised in a nice way, which is definitly necessary for something like this.
Current instructions it can parse and almost execute
{"constant","string"}
{"constant",1e5}
{"callMethod",instruction,method,args}
instruction: statement-ish thing
method: method name
args: table with statements for arguments
{"callConstructor",path,args}
path: Classpath of the class
args: table with statements for arguments
Need to use this to pick right constructor -- TODO
{"callFunction",path,args}
path: Path to the function
args: table with statements for arguments
Need to use this to pick right method if needed -- TODO
{"operator",operator,left,right}
operator: one of those: +,-,*,/,^,%,..
left/right: both an instruction
{"getField",path}
path: Path to the field-
The most difficult thing will be stuff like print(Some.Thing.Special)
.
Is ‘Some’ a class? A field or local variable?
Maybe ‘Some.Thing’ is a class (static reference).
I might just do “classes need to be CamelCase and packages/locals pascalCase” but eh…
Random dayz style buildings
Ah very nice. Yes good organization is criticle for this kind of thing, or complex logic bugs become impossible to find.
Well, the way I look at it is this: ‘.’ is an access operator, it tries to derive a value from it’s left operand given it’s right operand. To do this it has to examine it’s left “Thing” operand and see if the value “Special” means anything given it’s left operands context. So ‘.’ can check the type of “Thing” and if it’s a class look for a public method or field with the name “Special” and return that. If “Thing” is some kind of collection, like a table, it can use “Special” as an index on it. If “Thing” is something else, like the number ‘5’ then you have an error.
Once that ‘.’ operator has determined what Thing.Special evaluates to, the next ‘.’ can determine how to use those results to evaluate against it’s left operand “Some”. EDIT: Actually that’s backwards. “Some.Thing” must be evaluated first, then “Special” must be evaluated against that.
I’m trying to make my tokenizer/parser/VM as generic as possible so I can use it to run different languages. I plan on releasing the tokenizer and parser publicly when I think they’re ready. As an example here’s the constructor for the test VM and some sample tests I run with it.
For some context
LEXX: is the lexical tokenizer that feeds tokens to the parser. I’ve put it on GitHub if you want to take a look, though it’s a bit rough around the edges still.
JUNO: is a Pratt style recursive decent parser
JUNO.JunoVM: is a base class for making VM’s given the results of a JUNO parse.
local TestVM = {}
setmetatable(TestVM, {__index = JUNO.JunoVM})
function TestVM.new()
local self = setmetatable({}, {__index = TestVM})
self.lexxMatchers = {LEXX.KeywordMatcher.new({"and", "or"}), LEXX.OperatorMatcher.new({"+", "-", "*", "/", "(", ")"}), LEXX.WhitespaceMatcher.new(), LEXX.IntegerMatcher.new(), LEXX.FloatMatcher.new()}
self.prefixParselets = {}
self.prefixParselets[LEXX.Tokens.FLOAT] = JUNO.NodeParselet.new(PrefixNumberNode, JUNO.Precedence.PREFIX)
self.prefixParselets[LEXX.Tokens.INTEGER] = JUNO.NodeParselet.new(PrefixNumberNode, JUNO.Precedence.PREFIX)
self.prefixParselets[LEXX.Tokens.WHITESPACE] = JUNO.PrefixNOPParselet.new(JUNO.Precedence.NOP)
self.prefixParselets[LEXX.Tokens.OPERATOR .. ":" .. "-"] = JUNO.PrefixParselet.new(PrefixNode, JUNO.Precedence.PREFIX)
self.prefixParselets[LEXX.Tokens.OPERATOR .. ":" .. "+"] = JUNO.PrefixParselet.new(PrefixNode, JUNO.Precedence.PREFIX)
self.prefixParselets[LEXX.Tokens.OPERATOR .. ":" .. "("] = JUNO.BlockParselet.new(JUNO.BlockNode, JUNO.Precedence.NOP, "(", ")")
self.infixParselets = {}
self.infixParselets[LEXX.Tokens.OPERATOR .. ":" .. "+"] = JUNO.InfixParselet.new(InfixMathNode, JUNO.Precedence.SUM)
self.infixParselets[LEXX.Tokens.OPERATOR .. ":" .. "-"] = JUNO.InfixParselet.new(InfixMathNode, JUNO.Precedence.SUM)
self.infixParselets[LEXX.Tokens.OPERATOR .. ":" .. "*"] = JUNO.InfixParselet.new(InfixMathNode, JUNO.Precedence.PRODUCT)
self.infixParselets[LEXX.Tokens.OPERATOR .. ":" .. "/"] = JUNO.InfixParselet.new(InfixMathNode, JUNO.Precedence.PRODUCT)
self.infixParselets[LEXX.Tokens.KEYWORD] = JUNO.InfixParselet.new(InfixBooleanNode, JUNO.Precedence.PREFIX)
return self
end
Here are a couple of my tests. The “rpn()” function dumps the parsed nodes in Reverse Polish Notation for a quick sanity check that they were parsed in the correct order and the resulting node structure is sound.
-- test that precedence works #2
testVM:compile("1 + 4 * 2 - 3 * 2 + 6 - 3")
result = testVM:rpn()
if (result ~= "142*+32*-6+3-") then print("ERROR: Expression rpn should have evaluated to '142*+32*-6+3-' but got: " .. result) end
result = testVM:run()
result = table.remove(result.heap)
if (result ~= 6) then print("ERROR: Expression should have evaluated to 6, but got: " .. result) end
-- test parentheses work
testVM:compile("1 * (2 + 3) - 4")
result = testVM:rpn()
if (result ~= "123+*4-") then print("ERROR: Expression rpn should have evaluated to '123+*4-' but got: " .. result) end
result = testVM:run()
result = table.remove(result.heap)
if (result ~= 1) then print("ERROR: Expression should have evaluated to 1, but got: " .. result) end
Started to work on a logo for Ultimate Boxing (my next game).
Not sure if there is anything I should really change, but I will be looking at it again tomorrow night.
Making a circuity simulator, complete with all the logic gates and eventually simple RAM and adders. Along with some extras like lights, pressure buttons, color customizable outputs, etc. You’ll be able to make simple neat things like calculators, if you know your stuff.
,
Turn it into a game where you have a limited amount of gates to solve some problem in each level
Sounds decent as a game, but I can’t be bothered to put a map like that together tbh, and I wouldn’t enjoy playing that myself. I got this idea from blockland with MeltingPlastic’s Logic Bricks mod, and I remember playing in servers where people were doing neat stuff with it and wanted to make that a possibility on roblox too.
That was with mirrors and light beams though wasn’t it
Yes but they use a similar concept.
I kind of realised something later on:
A “classpath” would only be used when a class is actually expected, like:
public class SomeClass extends CLASS {
private CLASS field = (CLASS) new CLASS();
public CLASS method (CLASS idk) {
CLASS local;
}
}
Although, tbh, in Java, people can also do stuff like:
return some.package.SomeClass.someMethod();
Your post actually gave me an idea how to fix this.
The most left thing, like ‘some’, I could see what that references to.
If there’s a field / local variable with that name, that’ll get priority.
If there isn’t, then I can assume it’s the start of a package path.
Only “issue” I can think of is that it might “block” some packages:
public static String net = "0.0.0.0";
static {
// This wouldn't work as 'net' is resolved as the field 'net'
print(net.website.api.SomeClass.static_field);
// This still works, as the syntax only expects a class reference
net.stuff.Class var = (net.stuff.Class) new net.stuff.Class();
}
Which is fine I guess.
Like you said, I could see that “net.website” isn’t a valid field of String and go from there, but eh.
It might just be a typo while referencing a field, the wanted package doesn’t exist, … too much work
Heh, name collisions happen all the time, particularly on big projects. This is one of the things naming conventions were created to solve. If a developer names a variable the same thing as a package they are importing, that’s their problem.
When resolving a name things are commonly checked in this order
local scope → containing scope chain (inner to outer) → global scope → keywords
sometimes keywords are checked first to prevent overloading core functionality of the language, but some languages consider being able to do this a plus.