view builder update

This commit is contained in:
2015-07-13 14:13:38 +03:00
parent f53b065d59
commit a060118589
6 changed files with 445 additions and 567 deletions

View File

@@ -1,160 +0,0 @@
package haxework.gui;
import haxework.resources.Resources.ResMap;
import haxework.resources.IResources;
import haxework.provider.Provider;
import openfl.Assets;
import haxework.locale.LString;
import haxe.Json;
import flash.errors.TypeError;
import flash.errors.Error;
enum Value {
SIMPLE(v:Dynamic);
RESOURCE(res:ResMap<Dynamic>, key:String);
LINK(key:String);
BUILDER(v:Builder);
ARRAY(v:Array<Value>);
}
class Field {
public var key(default, null):String;
public var value(default, null):Value;
public function new(key:String, value:Dynamic) {
this.key = key;
this.value = fieldValue(value);
}
public static function buildValue(value:Value, ?links:Dynamic):Dynamic {
return switch (value) {
case Value.SIMPLE(v):
v;
case Value.BUILDER(v):
v.build(links);
case Value.ARRAY(v):
v.map(function(i) return buildValue(i, links));
case Value.RESOURCE(r, k):
r.get(k);
case Value.LINK(k):
Reflect.field(links, k);
}
}
private static function specialValue(value):Value {
var a = value.split(":");
return switch (a[0]) {
case "asset":
switch (a[1]) {
case "image":
Value.SIMPLE(Assets.getBitmapData(a[2]));
case _:
Value.SIMPLE(value);
}
case "res":
//Reflect.field(Provider.get(IResources), a[1]).bind(a[2], object, field);
Value.RESOURCE(Reflect.field(Provider.get(IResources), a[1]), a[2]);
case "locale":
Value.SIMPLE(new LString(a[1]));
case "class":
Value.SIMPLE(Type.resolveClass(a[1]));
case "layout":
var data = Json.parse(Assets.getText(a[1]));
Value.BUILDER(new Builder(data));
case "link":
Value.LINK(a[1]); //ToDo
case _:
throw new Error("Unsupported prefix \"" + a[0] + "\"");
//Value.SIMPLE(value);
}
}
private static function fieldValue(value:Dynamic):Value {
return if (Std.is(value, Array)) {
Value.ARRAY(value.map(fieldValue));
} else if (Std.is(value, String)) {
if (value.charAt(0) == "@") {
specialValue(value.substr(1));
} else if (~/0x[A-Fa-f\d]{6}/.match(value)) {
Value.SIMPLE(Std.parseInt(value));
} else {
Value.SIMPLE(value);
}
} else if (Std.is(value, Float) || (Std.is(value, Bool))) {
Value.SIMPLE(value);
} else if (value != null) {
if (Reflect.hasField(value, "type")) {
Value.BUILDER(new Builder(value));
} else {
Value.SIMPLE(value);
}
} else {
Value.SIMPLE(value);
}
}
}
@:deprecated("Use haxework.gui.ViewBuilder")
class Builder {
private var factory:Class<Dynamic>;
private var fields:Map<String, Field>;
public function new(data:Dynamic) {
if (Reflect.hasField(data, "type")) {
var type = Reflect.field(data, "type");
this.factory = Type.resolveClass(type);
if (factory == null) throw new TypeError("Class \"" + type + "\" not found");
}
this.fields = new Map<String, Field>();
if (Reflect.hasField(data, "style")) {
var style = Provider.get(IResources).styles.get(Reflect.field(data, "style"));
for (key in Reflect.fields(style)) {
fields.set(key, new Field(key, Reflect.field(style, key)));
}
}
for (key in Reflect.fields(data)) {
switch (key) {
case "type":
case "style":
case _: fields.set(key, new Field(key, Reflect.field(data, key)));
}
}
}
public function build(?links:Dynamic):Dynamic {
var instance:Dynamic = Type.createInstance(factory, []);
instance = fill(instance, links);
var initMethod:Dynamic = Reflect.field(instance, "init");
if (initMethod != null) Reflect.callMethod(instance, initMethod, []);
return instance;
}
public function fill(instance:Dynamic, ?links:Dynamic):Dynamic {
for (field in fields) {
Reflect.setProperty(instance, field.key, Field.buildValue(field.value, links));
switch (field.value) {
case Value.RESOURCE(r, k):
r.bind(k, instance, field.key);
case _:
}
}
return instance;
}
private static var cache:Map<String, Builder> = new Map<String, Builder>();
public static function createFromAsset(asset:String, ?key:String):Dynamic {
var cacheKey = asset + ":" + key;
if (!cache.exists(cacheKey)) {
var data = Json.parse(openfl.Assets.getText(asset));
if (key != null) {
data = Reflect.field(data, key);
}
cache.set(cacheKey, new Builder(data));
}
return cache.get(cacheKey);
}
}

View File

@@ -1,210 +0,0 @@
package haxework.gui;
//ToDo:
import haxework.locale.LString;
import haxe.Json;
import flash.errors.Error;
import openfl.Assets;
import flash.errors.TypeError;
import flash.errors.ArgumentError;
import haxework.resources.IResources;
import haxework.provider.Provider;
import haxework.gui.View;
import haxework.gui.SpriteView;
import haxework.gui.MovieView;
import haxework.gui.GroupView;
import haxework.gui.HGroupView;
import haxework.gui.VGroupView;
import haxework.gui.TextView;
import haxework.gui.InputView;
import haxework.gui.LabelView;
import haxework.gui.ButtonView;
import haxework.gui.ToggleButtonView;
import haxework.gui.ProgressView;
import haxework.gui.AnimateView;
import haxework.gui.skin.ColorSkin;
import haxework.gui.skin.BitmapSkin;
import haxework.gui.skin.ButtonColorSkin;
import haxework.gui.skin.ButtonBitmapSkin;
import haxework.gui.skin.ProgressSkin;
import haxework.gui.frame.FrameSwitcher;
@:deprecated("Use haxework.gui.ViewBuilder")
class GuiBuilder {
private static var cache:Map<String, Dynamic> = new Map<String, Dynamic>();
private static function getResource(resource:String):Dynamic {
if (!cache.exists(resource)) {
cache.set(resource, Json.parse(Assets.getText(resource)));
}
return cache.get(resource);
}
public static function build(data:Dynamic, ?links:Dynamic):Dynamic {
return new GuiB(data, links, data._includes).build();
}
public static function fill(object:Dynamic, data:Dynamic, ?links:Dynamic):Void {
new GuiF(object, data, links, data._includes).fill();
}
public static function buildFromAssets(resource:String, ?key:String = null, ?links:Dynamic = null):Dynamic {
var form:Dynamic = getResource(resource);
if (key != null) form = Reflect.field(form, key);
return build(form, links);
}
public static function fillFromAssets(object:Dynamic, resource:String, ?key:String = null, ?links:Dynamic = null):Void {
var form:Dynamic = getResource(resource);
if (key != null) form = Reflect.field(form, key);
fill(object, form, links);
}
}
class GuiB {
private var data:Dynamic;
private var links:Dynamic;
private var includes:Dynamic;
public function new(data:Dynamic, ?links:Dynamic, ?includes:Dynamic) {
this.data = data;
this.links = links;
this.includes = includes;
}
public function build():Dynamic {
if (Reflect.hasField(data, "type")) {
var type:String = data.type;
//Reflect.deleteField(data, "type");
if (type.charAt(0) == "~") return Type.resolveClass(type.substr(1));
var object:Dynamic = instance(type);
new GuiF(object, data, links, includes).fill();
var initMethod:Dynamic = Reflect.field(object, "init");
if (initMethod != null) Reflect.callMethod(object, initMethod, []);
return object;
} else if (Std.is(data, String)) {
return GuiF.convertString(data, links);
} else {
new GuiF(data, data, links, includes).fill();
return data;
}
}
private static function instance(type:String):Dynamic {
var clazz:Class<Dynamic> = Type.resolveClass(type);
if (clazz == null) throw new TypeError("Class \"" + type + "\" not found");
var instance:Dynamic = Type.createInstance(clazz, []);
return instance;
}
}
class GuiF {
private var object:Dynamic;
private var data:Dynamic;
private var links:Dynamic;
private var includes:Dynamic;
public function new(object:Dynamic, data:Dynamic, ?links:Dynamic, ?includes:Dynamic) {
this.object = object;
this.data = data;
this.links = links;
this.includes = includes;
}
public static function convertString(value:Dynamic, ?links:Dynamic):Dynamic {
var s:String = cast(value, String);
var c:String = s.charAt(0);
if (c == "#") {
value = Reflect.field(links, s.substr(1));
} else if (c == "~") {
var a:Array<String> = s.substr(1).split(":");
var e:Enum<Dynamic> = Type.resolveEnum(a[0]);
value = Type.createEnum(e, a[1]);
} else if (c == "@") {
if (s.charAt(1) == "~") {
var a:Array<String> = s.substr(2).split(":");
switch (a[0]) {
case "image": value = Assets.getBitmapData(a[1]);
case "layout": value = GuiBuilder.buildFromAssets(a[1], null, links);
}
} else {
var a:Array<String> = s.substr(1).split(":");
value = Reflect.field(Provider.get(IResources), a[0]).get(a[1]);
}
} else if (~/0x[A-Fa-f\d]{6}/.match(value)) {
value = Std.parseInt(value);
}
return value;
}
public function fill() {
if (Reflect.hasField(data, "style")) {
var style = Provider.get(IResources).styles.get(Reflect.field(data, "style"));
for (key in Reflect.fields(style)) if (!Reflect.hasField(data, key)) {
Reflect.setField(data, key, Reflect.field(style, key));
}
}
var fields:Array<String> = Reflect.fields(data);
for (field in fields) {
if (field == "type" || field == "style") continue;
if (field == "_includes") continue;
var value:Dynamic = Reflect.field(data, field);
if (field == "_include") {
var data:Dynamic = includes == null ? null : Reflect.field(includes, value);
if (data != null) {
new GuiF(object, data, links).fill();
}
continue;
}
if (Std.is(value, Array)) {
var a:Array<Dynamic> = [];
for (o in cast(value, Array<Dynamic>)) a.push(new GuiB(o, links, includes).build());
value = a;
} else if (Std.is(value, String)) {
var s:String = cast(value, String);
var c:String = s.charAt(0);
if (c == "#") {
value = Reflect.field(links, s.substr(1));
} else if (c == "~") {
var a:Array<String> = s.substr(1).split(":");
var e:Enum<Dynamic> = Type.resolveEnum(a[0]);
value = Type.createEnum(e, a[1]);
} else if (c == "@") {
var c1 = s.charAt(1);
if (c1 == "~") {
var a:Array<String> = s.substr(2).split(":");
switch (a[0]) {
case "image": value = Assets.getBitmapData(a[1]);
}
} else if (c1 == "^") {
value = new LString(s.substr(2));
} else {
var a:Array<String> = s.substr(1).split(":");
//value = Reflect.field(Provider.get(IResources), a[0]).get(a[1]);
Reflect.field(Provider.get(IResources), a[0]).bind(a[1], object, field);
continue;
}
} else if (~/0x[A-Fa-f\d]{6}/.match(value)) {
value = Std.parseInt(value);
}
} else if (Std.is(value, Float)) {
} else if (Std.is(value, Bool)) {
} else {
var o:Dynamic = new GuiB(value, links, includes).build();
new GuiF(o, value, links).fill();
value = o;
}
try {
Reflect.setProperty(object, field, value);
} catch(error:Dynamic) {
L.e("GuiBuilder", "", error);
}
}
}
}

View File

@@ -1,206 +1,10 @@
package haxework.gui; package haxework.gui;
import haxe.Json; import haxework.gui.build.Builder;
import haxe.macro.Expr;
import haxe.macro.Context;
@:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build()) @:remove @:autoBuild(haxework.gui.ViewBuilderImpl.build())
extern interface ViewBuilder {} extern interface ViewBuilder {}
#if macro
private class BuilderUtil {
public static function loadFile(path:String, json:Bool = true) {
Context.registerModuleDependency(Context.getLocalModule(), path);
var content = sys.io.File.getContent(path);
return json ? Json.parse(content) : content;
}
public static function getMeta(key:String):Array<String> {
var c = Context.getLocalClass().get();
for (meta in c.meta.get()) {
if (meta.name == key) {
return meta.params.map(function(param) return switch(param.expr) {
case ExprDef.EConst(Constant.CString(value)): value;
case _: null;
});
}
}
return [];
}
}
private class Builder {
private var templateFile:String;
private var templateKey:String;
private var styleFile:String;
private var template:Dynamic;
private var fields:Array<Field>;
private var exprs:Array<Expr>;
private var style:Dynamic;
private var i:Int;
public function new() {
var templateMeta = BuilderUtil.getMeta(":template");
var templatePath = templateMeta[0].split("@");
templateFile = Context.resolvePath(templatePath[0]);
templateKey = templatePath[1];
template = BuilderUtil.loadFile(templateFile);
if (templateKey != null) template = Reflect.field(template, templateKey);
if (templateMeta[1] != null) {
styleFile = Context.resolvePath(templateMeta[1]);
style = BuilderUtil.loadFile(styleFile);
}
fields = Context.getBuildFields();
exprs = [];
i = 0;
}
private function getPosition():Position {
return Context.makePosition({min:0, max:0, file:templateFile + (templateKey != null ? "@" + templateKey : "")});
}
private function specialValue(name:String, key:String, a:Array<String>):Dynamic {
return switch (a[0]) {
case "asset":
switch (a[1]) {
case "image":
"openfl.Assets.getBitmapData(\"" + a[2] + "\")";
case _:
a[2];
}
case "res":
var res = "haxework.provider.Provider.get(haxework.resources.IResources)." + a[1];
var bindExpr = res + ".bind(\"" + a[2] + "\", " + name + ", \"" + key + "\")";
exprs.push(Context.parse(bindExpr, getPosition()));
//res + ".get(\"" + a[2] + "\")";
null;
case "locale":
"new haxework.locale.LString(\"" + a[1] + "\")";
case "class":
a[1];
case "layout":
var template = BuilderUtil.loadFile(a[1]);
return getValue(name, key, template);
case "link":
"(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")";
case _:
throw "Unsupported prefix \"" + a[0] + "\"";
}
}
private function getValue(name:String, key:String, value:Dynamic):Dynamic {
return if (Std.is(value, Array)) {
value.map(function(v) { return getValue(null, null, v); });
} else if (Std.is(value, String)) {
if (value.charAt(0) == "@") {
specialValue(name, key, value.substring(1, value.length).split(":"));
} else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) {
Std.parseInt(StringTools.replace(Std.string(value), "#", "0x"));
} else {
"\"" + value + "\"";
}
} else if (Std.is(value, Float) || (Std.is(value, Bool))) {
value;
} else if (value != null) {
if (Reflect.hasField(value, "type")) {
var n = "a" + i++;
var type = Reflect.field(value, "type");
exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition()));
createElement(value, n);
n;
} else {
value;
}
} else {
value;
}
}
private function createElement(template:Dynamic, name:String):String {
if (Reflect.hasField(template, "style")) {
var s = Reflect.field(style, Reflect.field(template, "style"));
for (key in Reflect.fields(s)) if (!Reflect.hasField(template, key)) {
Reflect.setField(template, key, Reflect.field(s, key));
}
}
if (Reflect.hasField(template, "id")) {
var id = Reflect.field(template, "id");
var type = Reflect.field(template, "type");
var expr = Context.parse("var a:" + type, getPosition());
var type = switch (expr.expr) {
case EVars(vars): vars[0].type;
case _: null;
}
fields.push({
name: id,
access: [APublic],
pos: getPosition(),
kind: FProp("default", "null", type)
});
exprs.push(Context.parse("this." + id + " = " + name, getPosition()));
}
for (key in Reflect.fields(template)) {
if (["type", "style"].indexOf(key) > -1) continue;
var value = getValue(name, key, Reflect.field(template, key));
if (value != null) {
exprs.push(Context.parse(name + "." + key + " = " + value, getPosition()));
}
}
return name;
}
public function build():Array<Field> {
createElement(template, "this");
var init = false;
for (f in fields) if (f.name == "init") {
init = true;
break;
}
fields.push({
name: "build",
access: [APublic],
pos: getPosition(),
kind: FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
expr: macro $b{exprs},
params: [],
ret: null
})
});
var contstrExprs = [];
contstrExprs.push(macro super());
contstrExprs.push(macro build(links));
if (init) contstrExprs.push(macro init());
fields.push({
name: "new",
access: [APublic],
pos: getPosition(),
kind: FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
expr: macro $b{contstrExprs},
params: [],
ret: null
})
});
return fields;
}
}
#end
class ViewBuilderImpl { class ViewBuilderImpl {
#if macro #if macro
public static function build() { public static function build() {

181
haxework/gui/build/Builder.hx Executable file
View File

@@ -0,0 +1,181 @@
package haxework.gui.build;
#if macro
import haxe.macro.Context;
import haxework.gui.build.PositionJsonParser;
import haxe.macro.Expr;
import haxe.macro.Expr.Field;
class Builder {
private var templateFile:String;
private var templateKey:String;
private var styleFile:String;
private var template:Dynamic;
private var fields:Array<Field>;
private var exprs:Array<Expr>;
private var style:Dynamic;
private var i:Int;
public function new() {
var templateMeta = BuilderUtil.getMeta(":template");
var templatePath = templateMeta[0].split("@");
templateFile = Context.resolvePath(templatePath[0]);
templateKey = templatePath[1];
template = BuilderUtil.loadFile(templateFile);
if (templateKey != null) template = Reflect.field(template, templateKey);
if (templateMeta[1] != null) {
styleFile = Context.resolvePath(templateMeta[1]);
style = BuilderUtil.loadFile(styleFile);
}
fields = Context.getBuildFields();
exprs = [];
i = 0;
}
private function getPosition(?position:JsonKeyPosition):Position {
var min = position == null ? 1 : position.min + 32; // :-(
var max = position == null ? 1 : position.max + 32;
return Context.makePosition({min:min, max:max, file:templateFile});
}
private function specialValue(name:String, key:String, a:Array<String>):Dynamic {
return switch (a[0]) {
case "asset":
switch (a[1]) {
case "image":
"openfl.Assets.getBitmapData(\"" + a[2] + "\")";
case _:
a[2];
}
case "res":
var res = "haxework.provider.Provider.get(haxework.resources.IResources)." + a[1];
var bindExpr = res + ".bind(\"" + a[2] + "\", " + name + ", \"" + key + "\")";
exprs.push(Context.parse(bindExpr, getPosition()));
//res + ".get(\"" + a[2] + "\")";
null;
case "locale":
"new haxework.locale.LString(\"" + a[1] + "\")";
case "class":
a[1];
case "layout":
var template = BuilderUtil.loadFile(a[1]);
return getValue(name, key, template);
case "link":
"(links == null) ? untyped this : Reflect.field(links, \"" + a[1] + "\")";
case _:
throw "Unsupported prefix \"" + a[0] + "\"";
}
}
private function getValue(name:String, key:String, value:Dynamic):Dynamic {
return if (Std.is(value, Array)) {
value.map(function(v) { return getValue(null, null, v); });
} else if (Std.is(value, String)) {
if (value.charAt(0) == "@") {
specialValue(name, key, value.substring(1, value.length).split(":"));
} else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) {
Std.parseInt(StringTools.replace(Std.string(value), "#", "0x"));
} else {
"\"" + value + "\"";
}
} else if (Std.is(value, Float) || (Std.is(value, Bool))) {
value;
} else if (value != null) {
if (Reflect.hasField(value, "type")) {
var n = "a" + i++;
var type = Reflect.field(value, "type");
exprs.push(Context.parse("var " + n + " = new " + type + "()", getPosition()));
createElement(value, n);
n;
} else {
value;
}
} else {
value;
}
}
private function createElement(template:Dynamic, name:String):String {
if (Reflect.hasField(template, "style")) {
var s = Reflect.field(style, Reflect.field(template, "style"));
for (key in Reflect.fields(s)) if (!Reflect.hasField(template, key)) {
Reflect.setField(template, key, Reflect.field(s, key));
}
}
if (Reflect.hasField(template, "id")) {
var id = Reflect.field(template, "id");
var type = Reflect.field(template, "type");
var expr = Context.parse("var a:" + type, getPosition());
var type = switch (expr.expr) {
case EVars(vars): vars[0].type;
case _: null;
}
fields.push({
name: id,
access: [APublic],
pos: getPosition(),
kind: FProp("default", "null", type)
});
exprs.push(Context.parse("this." + id + " = " + name, getPosition()));
}
for (key in Reflect.fields(template)) {
if (key.charAt(0) == "$" || ["type", "style"].indexOf(key) > -1) continue;
var position = Reflect.field(template, "$" + key);
var value = getValue(name, key, Reflect.field(template, key));
if (value != null) {
exprs.push(Context.parse(name + "." + key + " = " + value, getPosition(position)));
}
}
return name;
}
public function build():Array<Field> {
createElement(template, "this");
var init = false;
for (f in fields) if (f.name == "init") {
init = true;
break;
}
fields.push({
name: "build",
access: [APublic],
pos: getPosition(),
kind: FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
expr: macro $b{exprs},
params: [],
ret: null
})
});
var contstrExprs = [];
contstrExprs.push(macro super());
contstrExprs.push(macro build(links));
if (init) contstrExprs.push(macro init());
fields.push({
name: "new",
access: [APublic],
pos: getPosition(),
kind: FFun({
args: [{name:"links", type:TPath({name:"Dynamic", pack:[], params:[]}), opt:true, value:null}],
expr: macro $b{contstrExprs},
params: [],
ret: null
})
});
return fields;
}
}
#end

View File

@@ -0,0 +1,30 @@
package haxework.gui.build;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr.Constant;
import haxe.macro.Expr.ExprDef;
class BuilderUtil {
public static function loadFile(path:String, json:Bool = true) {
Context.registerModuleDependency(Context.getLocalModule(), path);
var content = sys.io.File.getContent(path);
Context.parse(content, Context.makePosition({min:0, max:0, file:path}));
return json ? PositionJsonParser.parse(content) : content;
}
public static function getMeta(key:String):Array<String> {
var c = Context.getLocalClass().get();
for (meta in c.meta.get()) {
if (meta.name == key) {
return meta.params.map(function(param) return switch(param.expr) {
case ExprDef.EConst(Constant.CString(value)): value;
case _: null;
});
}
}
return [];
}
}
#end

View File

@@ -0,0 +1,233 @@
package haxework.gui.build;
typedef JsonKeyPosition = {
var min:Int;
var max:Int;
}
class PositionJsonParser {
static public inline function parse(str : String) : Dynamic {
return new PositionJsonParser(str).parseRec();
}
var str : String;
var pos : Int;
function new( str : String ) {
this.str = str;
this.pos = 0;
}
function getKeyPosition():JsonKeyPosition {
return {
min: pos,
max: pos
}
}
function parseRec() : Dynamic {
while( true ) {
var c = nextChar();
switch( c ) {
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
// loop
case '{'.code:
var obj = {}, field = null, comma : Null<Bool> = null;
var position = null;
while( true ) {
var c = nextChar();
switch( c ) {
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
// loop
case '}'.code:
if( field != null || comma == false )
invalidChar();
return obj;
case ':'.code:
if( field == null )
invalidChar();
Reflect.setField(obj,field,parseRec());
Reflect.setField(obj,"$" + field,position);
field = null;
comma = true;
case ','.code:
if( comma ) comma = false else invalidChar();
case '"'.code:
if( comma ) invalidChar();
position = getKeyPosition();
field = parseString();
default:
invalidChar();
}
}
case '['.code:
var arr = [], comma : Null<Bool> = null;
while( true ) {
var c = nextChar();
switch( c ) {
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
// loop
case ']'.code:
if( comma == false ) invalidChar();
return arr;
case ','.code:
if( comma ) comma = false else invalidChar();
default:
if( comma ) invalidChar();
pos--;
arr.push(parseRec());
comma = true;
}
}
case 't'.code:
var save = pos;
if( nextChar() != 'r'.code || nextChar() != 'u'.code || nextChar() != 'e'.code ) {
pos = save;
invalidChar();
}
return true;
case 'f'.code:
var save = pos;
if( nextChar() != 'a'.code || nextChar() != 'l'.code || nextChar() != 's'.code || nextChar() != 'e'.code ) {
pos = save;
invalidChar();
}
return false;
case 'n'.code:
var save = pos;
if( nextChar() != 'u'.code || nextChar() != 'l'.code || nextChar() != 'l'.code ) {
pos = save;
invalidChar();
}
return null;
case '"'.code:
return parseString();
case '0'.code, '1'.code,'2'.code,'3'.code,'4'.code,'5'.code,'6'.code,'7'.code,'8'.code,'9'.code,'-'.code:
return parseNumber(c);
default:
invalidChar();
}
}
}
function parseString() {
var start = pos;
var buf = null;
while( true ) {
var c = nextChar();
if( c == '"'.code )
break;
if( c == '\\'.code ) {
if (buf == null) {
buf = new StringBuf();
}
buf.addSub(str,start, pos - start - 1);
c = nextChar();
switch( c ) {
case "r".code: buf.addChar("\r".code);
case "n".code: buf.addChar("\n".code);
case "t".code: buf.addChar("\t".code);
case "b".code: buf.addChar(8);
case "f".code: buf.addChar(12);
case "/".code, '\\'.code, '"'.code: buf.addChar(c);
case 'u'.code:
var uc = Std.parseInt("0x" + str.substr(pos, 4));
pos += 4;
#if (neko || php || cpp)
if( uc <= 0x7F )
buf.addChar(uc);
else if( uc <= 0x7FF ) {
buf.addChar(0xC0 | (uc >> 6));
buf.addChar(0x80 | (uc & 63));
} else if( uc <= 0xFFFF ) {
buf.addChar(0xE0 | (uc >> 12));
buf.addChar(0x80 | ((uc >> 6) & 63));
buf.addChar(0x80 | (uc & 63));
} else {
buf.addChar(0xF0 | (uc >> 18));
buf.addChar(0x80 | ((uc >> 12) & 63));
buf.addChar(0x80 | ((uc >> 6) & 63));
buf.addChar(0x80 | (uc & 63));
}
#else
buf.addChar(uc);
#end
default:
throw "Invalid escape sequence \\" + String.fromCharCode(c) + " at position " + (pos - 1);
}
start = pos;
}
#if (neko || php || cpp)
// ensure utf8 chars are not cut
else if( c >= 0x80 ) {
pos++;
if( c >= 0xFC ) pos += 4;
else if( c >= 0xF8 ) pos += 3;
else if( c >= 0xF0 ) pos += 2;
else if( c >= 0xE0 ) pos++;
}
#end
else if( StringTools.isEof(c) )
throw "Unclosed string";
}
if (buf == null) {
return str.substr(start, pos - start - 1);
}
else {
buf.addSub(str,start, pos - start - 1);
return buf.toString();
}
}
inline function parseNumber( c : Int ) : Dynamic {
var start = pos - 1;
var minus = c == '-'.code, digit = !minus, zero = c == '0'.code;
var point = false, e = false, pm = false, end = false;
while( true ) {
c = nextChar();
switch( c ) {
case '0'.code :
if (zero && !point) invalidNumber(start);
if (minus) {
minus = false; zero = true;
}
digit = true;
case '1'.code,'2'.code,'3'.code,'4'.code,'5'.code,'6'.code,'7'.code,'8'.code,'9'.code :
if (zero && !point) invalidNumber(start);
if (minus) minus = false;
digit = true; zero = false;
case '.'.code :
if (minus || point) invalidNumber(start);
digit = false; point = true;
case 'e'.code, 'E'.code :
if (minus || zero || e) invalidNumber(start);
digit = false; e = true;
case '+'.code, '-'.code :
if (!e || pm) invalidNumber(start);
digit = false; pm = true;
default :
if (!digit) invalidNumber(start);
pos--;
end = true;
}
if (end) break;
}
var f = Std.parseFloat(str.substr(start, pos - start));
var i = Std.int(f);
return if( i == f ) i else f;
}
inline function nextChar() {
return StringTools.fastCodeAt(str,pos++);
}
function invalidChar() {
pos--; // rewind
throw "Invalid char "+StringTools.fastCodeAt(str,pos)+" at position "+pos;
}
function invalidNumber( start : Int ) {
throw "Invalid number at position "+start+": " + str.substr(start, pos - start);
}
}