Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial

  • Loading branch information...
commit 7a11665731d74b8c05c2854bbb6caeed61c109ab 0 parents
Simn-haxe2 authored April 16, 2012
7  .gitignore
... ...
@@ -0,0 +1,7 @@
  1
+
  2
+/bin/php
  3
+/bin/js.js
  4
+/bin/neko.n
  5
+/bin/swf8.swf
  6
+/bin/swf9.swf
  7
+/bin/cpp
11  bin/js.html
... ...
@@ -0,0 +1,11 @@
  1
+<!DOCTYPE html>
  2
+<html lang="en">
  3
+<head>
  4
+	<meta charset="utf-8">
  5
+	<title></title>
  6
+</head>
  7
+<body>
  8
+	<div id="haxe:trace"></div>
  9
+	<script src="js.js"></script>
  10
+</body>
  11
+</html>
23  build.hxml
... ...
@@ -0,0 +1,23 @@
  1
+build_base.hxml
  2
+-neko bin/neko.n
  3
+
  4
+--next
  5
+build_base.hxml
  6
+-swf bin/swf9.swf
  7
+
  8
+--next
  9
+build_base.hxml
  10
+-swf-version 8
  11
+-swf bin/swf8.swf
  12
+
  13
+--next
  14
+build_base.hxml
  15
+-php bin/php
  16
+
  17
+--next
  18
+build_base.hxml
  19
+-js bin/js.js
  20
+
  21
+--next
  22
+build_base.hxml
  23
+-cpp bin/cpp
4  build_base.hxml
... ...
@@ -0,0 +1,4 @@
  1
+-cp src
  2
+-cp test
  3
+-lib tink_macros
  4
+-main TestMain
56  selecthxml.hxproj
... ...
@@ -0,0 +1,56 @@
  1
+<?xml version="1.0" encoding="utf-8"?>
  2
+<project version="2">
  3
+  <!-- Output SWF options -->
  4
+  <output>
  5
+    <movie outputType="CustomBuild" />
  6
+    <movie input="" />
  7
+    <movie path="bin\swf9.swf" />
  8
+    <movie fps="30" />
  9
+    <movie width="800" />
  10
+    <movie height="600" />
  11
+    <movie version="9" />
  12
+    <movie minorVersion="0" />
  13
+    <movie platform="Flash Player" />
  14
+    <movie background="#FFFFFF" />
  15
+  </output>
  16
+  <!-- Other classes to be compiled into your SWF -->
  17
+  <classpaths>
  18
+    <class path="src" />
  19
+    <class path="test" />
  20
+  </classpaths>
  21
+  <!-- Build options -->
  22
+  <build>
  23
+    <option directives="" />
  24
+    <option flashStrict="False" />
  25
+    <option mainClass="TestMain" />
  26
+    <option enabledebug="False" />
  27
+    <option additional="" />
  28
+  </build>
  29
+  <!-- haxelib libraries -->
  30
+  <haxelib>
  31
+    <library name="tink_macros" />
  32
+  </haxelib>
  33
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
  34
+  <compileTargets>
  35
+    <!-- example: <compile path="..." /> -->
  36
+  </compileTargets>
  37
+  <!-- Assets to embed into the output SWF -->
  38
+  <library>
  39
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
  40
+  </library>
  41
+  <!-- Paths to exclude from the Project Explorer tree -->
  42
+  <hiddenPaths>
  43
+    <!-- example: <hidden path="..." /> -->
  44
+  </hiddenPaths>
  45
+  <!-- Executed before build -->
  46
+  <preBuildCommand>haxe build.hxml</preBuildCommand>
  47
+  <!-- Executed after build -->
  48
+  <postBuildCommand alwaysRun="False" />
  49
+  <!-- Other project options -->
  50
+  <options>
  51
+    <option showHiddenPaths="False" />
  52
+    <option testMovie="Default" />
  53
+  </options>
  54
+  <!-- Plugin storage -->
  55
+  <storage />
  56
+</project>
84  src/selecthxml/SelectDom.hx
... ...
@@ -0,0 +1,84 @@
  1
+package selecthxml;
  2
+
  3
+import selecthxml.engine.Parser;
  4
+import selecthxml.engine.Type;
  5
+import selecthxml.engine.XmlDom;
  6
+import selecthxml.TypedXml;
  7
+
  8
+#if macro
  9
+
  10
+import selecthxml.engine.MacroHelper;
  11
+import haxe.macro.Context;
  12
+import haxe.macro.Expr;
  13
+
  14
+#else
  15
+
  16
+import selecthxml.engine.RegexLexer;
  17
+import selecthxml.engine.SelectEngine;
  18
+
  19
+#end
  20
+
  21
+class SelectDom
  22
+{
  23
+	#if !macro @:macro #end
  24
+	static public function select<T>(xml:ExprRequire<TypedXml<T>>, selectionString:String)
  25
+	{		
  26
+		if (selectionString == null || selectionString.length == 0)
  27
+			return Context.error("Selection string expected.", xml.pos);
  28
+						
  29
+		return MacroHelper.makeFunction(xml, selectionString);
  30
+	}
  31
+	
  32
+	@:allowConstraint static public inline function getXml<T:Xml>(result:TypedResult<T>):T
  33
+		return untyped result.__x_m_l__
  34
+		
  35
+	#if !macro
  36
+	
  37
+	static var lastXmlDom:XmlDom;
  38
+	static var selectorCache = new Hash<Selector>();
  39
+	
  40
+	static public function runtimeSelect(xml:Xml, selector:String)
  41
+	{
  42
+		if (lastXmlDom == null || lastXmlDom.xml != xml)
  43
+			lastXmlDom = new XmlDom(xml);
  44
+
  45
+		var xmlDom = lastXmlDom;
  46
+		
  47
+		if (!selectorCache.exists(selector))
  48
+		{
  49
+			var lexer = new RegexLexer(selector);
  50
+			var parser = new Parser(lexer);
  51
+			selectorCache.set(selector, parser.parse());
  52
+		}
  53
+		
  54
+		var s = selectorCache.get(selector);
  55
+
  56
+		if (isIdOnly(s))
  57
+		{
  58
+			var dom = xmlDom.getElementById(s[0].id);
  59
+			if (dom == null) return [];
  60
+			return [dom.xml];
  61
+		}
  62
+		
  63
+		var engine = new SelectEngine();
  64
+		var result = engine.query(s, xmlDom);
  65
+		var ret = [];
  66
+		for (r in result)
  67
+			ret.push(cast(r, XmlDom).xml);
  68
+		return ret;
  69
+	}
  70
+
  71
+	static inline function isIdOnly(s:Selector):Bool
  72
+	{
  73
+		var p = s[0];		
  74
+		return s.length == 1 
  75
+			&& p.id != null 
  76
+			&& p.tag == null 
  77
+			&& p.classes.length == 0
  78
+			&& p.attrs.length == 0
  79
+			&& p.pseudos.length == 0
  80
+			&& p.combinator == null;
  81
+	}
  82
+	
  83
+	#end
  84
+}
11  src/selecthxml/TypedResult.hx
... ...
@@ -0,0 +1,11 @@
  1
+package selecthxml;
  2
+
  3
+class TypedResult<T> implements Dynamic
  4
+{
  5
+	var __x_m_l__:Xml;
  6
+	
  7
+	public function new(xml:Xml)
  8
+	{
  9
+		__x_m_l__ = xml;
  10
+	}
  11
+}
3  src/selecthxml/TypedXml.hx
... ...
@@ -0,0 +1,3 @@
  1
+package selecthxml;
  2
+
  3
+typedef TypedXml<T> = Xml;
131  src/selecthxml/engine/Lexer.hx
... ...
@@ -0,0 +1,131 @@
  1
+package selecthxml.engine;
  2
+import selecthxml.engine.Type;
  3
+import haxe.io.Input;
  4
+
  5
+class Lexer {
  6
+	var input:Input;
  7
+	var currentPos:Int;
  8
+	var buffer:Array<Int>;
  9
+
  10
+	public function new(input:Input) {
  11
+		this.input = input;
  12
+		buffer = [];
  13
+		currentPos = 1;
  14
+	}
  15
+	
  16
+	public function readToken():Token {		
  17
+		var min = currentPos;
  18
+		var token = readTokenDef();
  19
+		var max = currentPos;
  20
+		return { def: token, pos: {min: min, max: max} }
  21
+	}
  22
+	
  23
+	public function readTokenDef():TokenDef {
  24
+		var c = readChar();
  25
+		switch(c) {
  26
+			case 0: return TEof;
  27
+			case ".".code:  return TDot;
  28
+			case "#".code:  return THash;
  29
+			case ":".code:  return TColon;
  30
+			case "*".code:  return TAsterisk;
  31
+			case "[".code:  return TSquareBrkOpen;
  32
+			case "]".code:  return TSquareBrkClose;
  33
+			case "(".code:  return TParenOpen;
  34
+			case ")".code:  return TParenClose;
  35
+			case "\"".code: return readString("\"".code);
  36
+			case "'".code:  return readString("'".code);
  37
+			case ">".code: return TGt;
  38
+			case "+".code: return TPlus;
  39
+			case "-".code: return TMinus;
  40
+			case "~".code: return TTilde;
  41
+			case "^".code: return TCaret;
  42
+			case "$".code: return TDollar;
  43
+			case "|".code: return TPipe;
  44
+			case "=".code: return TEquals;
  45
+			case "_".code: return TUnderscore;
  46
+			default:
  47
+				// Alpha a-zA-Z
  48
+				if (isAlpha(c)) {
  49
+					var str = "";
  50
+					while (true) {
  51
+						str += String.fromCharCode(c);
  52
+						c = readChar();
  53
+						if (!isAlpha(c)) {
  54
+							pushChar(c);
  55
+							break;
  56
+						}						
  57
+					}
  58
+					return TAlpha(str);
  59
+				}
  60
+				// Number 0-9
  61
+				else if (isNumber(c)) {
  62
+					var str = "";
  63
+					while (true) {
  64
+						str += String.fromCharCode(c);
  65
+						c = readChar();
  66
+						if (!isNumber(c)) {
  67
+							pushChar(c);
  68
+							break;
  69
+						}		
  70
+					}
  71
+					return TInteger(Std.parseInt(str));
  72
+				}
  73
+				else if (isWhitespace(c)) {
  74
+					while (true) {
  75
+						c = readChar();
  76
+						if (!isWhitespace(c)) {
  77
+							pushChar(c);
  78
+							break;					
  79
+						}
  80
+					}
  81
+					return TWhitespace;
  82
+				}
  83
+				throw EInvalidCharacter(String.fromCharCode(c), { min: currentPos - 1, max: currentPos });
  84
+				return null;
  85
+		}
  86
+	}	
  87
+	
  88
+	function readString(quoteType:Int) {
  89
+		var str = "";
  90
+		var esc = false;
  91
+		var startPos = currentPos;
  92
+		while (true) {
  93
+			var c = readChar();
  94
+			if (esc == true) {
  95
+				esc = false;
  96
+				if (c == quoteType) {
  97
+					str += String.fromCharCode(c);
  98
+				}						
  99
+			}					
  100
+			else if (c == "\\".code) esc = true;
  101
+			else if (c == quoteType) break;
  102
+			else if (c == 0) throw EUnterminatedString({ min:startPos - 1, max:startPos });
  103
+			else str += String.fromCharCode(c);
  104
+		}
  105
+		return TString(str);
  106
+	}
  107
+	
  108
+	inline function isAlpha(c:Int) { 
  109
+		return (c >= "A".code && c <= "Z".code) || (c >= "a".code && c <= "z".code); 
  110
+	}
  111
+	
  112
+	inline function isNumber(c:Int) {
  113
+		return c >= "0".code && c <= "9".code;
  114
+	}
  115
+	
  116
+	inline function isWhitespace(c:Int) {
  117
+		return c == 32 || c == 9 || c == 13;
  118
+	}
  119
+	
  120
+	function readChar():Int {
  121
+		currentPos++;
  122
+		if (buffer.length > 0)
  123
+			return buffer.shift();		
  124
+		try { return input.readByte(); } catch (ex:Dynamic) { return 0; }
  125
+	}
  126
+	
  127
+	function pushChar(char:Int):Void {
  128
+		currentPos--;
  129
+		buffer.push(char);
  130
+	}
  131
+}
255  src/selecthxml/engine/MacroHelper.hx
... ...
@@ -0,0 +1,255 @@
  1
+package selecthxml.engine;
  2
+
  3
+#if macro
  4
+import haxe.macro.Expr;
  5
+import haxe.macro.Type;
  6
+import haxe.macro.Context;
  7
+
  8
+import selecthxml.engine.Type;
  9
+import selecthxml.TypedXml;
  10
+
  11
+import tink.macro.tools.TypeTools;
  12
+import tink.macro.tools.ExprTools;
  13
+import tink.macro.tools.FunctionTools;
  14
+import tink.core.types.Outcome;
  15
+
  16
+using tink.macro.tools.ExprTools;
  17
+using tink.macro.tools.TypeTools;
  18
+using tink.core.types.Outcome;
  19
+#end
  20
+
  21
+/**
  22
+ * ...
  23
+ * @author Simon Krajewski
  24
+ */
  25
+
  26
+class MacroHelper 
  27
+{
  28
+	#if macro
  29
+	
  30
+	static var extensionCache = new Hash<ComplexType>();
  31
+	
  32
+	static public function makeFunction(xml:Expr, selectionString:String)
  33
+	{
  34
+		var lexer = new Lexer(new haxe.io.StringInput(selectionString));
  35
+		var parser = new Parser(lexer);
  36
+		var selector = parser.parse();
  37
+		
  38
+		var ctype = TPath( { name: "Xml", pack: [], sub: null, params: [] } );
  39
+		
  40
+		var baseReturn = "selecthxml.SelectDom.runtimeSelect".resolve().call([xml, selectionString.toExpr()]);
  41
+		var funcExpr = switch(getXmlType(Context.typeof(xml), selector))
  42
+		{
  43
+			case Success(t):
  44
+				
  45
+				var fields = t.type.getFields().sure();
  46
+				if (fields.length > 0)
  47
+				{
  48
+					var fullName = (t.basePack.length > 0 ? t.basePack.join(".") + "." : "")
  49
+						+ t.baseName.charAt(0).toLowerCase() + t.baseName.substr(1)
  50
+						+ "." +t.name.charAt(0).toUpperCase() + t.name.substr(1);
  51
+					if (!extensionCache.exists(fullName))
  52
+						extensionCache.set(fullName, createExtension( { name: "TypedResult", pack:["selecthxml"], params:[], sub:null }, fields, t));
  53
+					ctype = extensionCache.get(fullName);
  54
+					if (haxe.macro.Context.defined('display'))
  55
+						EReturn("null".resolve()).at();
  56
+					else
  57
+						[
  58
+							"xmls".define(baseReturn),
  59
+							"ret".define([].toExpr()),
  60
+							"xmls".resolve().iterate(
  61
+								"ret".resolve().field("push").call([("selecthxml.types." +fullName).instantiate(["xml".resolve()])])
  62
+							, "xml"),
  63
+							EReturn(isSingular(selector) ? "ret".resolve().field("shift").call() : "ret".resolve()).at()
  64
+						].toBlock();
  65
+				}
  66
+			default:
  67
+				EReturn(isSingular(selector) ? baseReturn.field("shift").call() : baseReturn).at();
  68
+		}
  69
+
  70
+		return EFunction(null, FunctionTools.func(funcExpr, [], isSingular(selector) ? ctype : "Array".asComplexType([TPType(ctype)]), null, false)).at().call([]);	
  71
+	}
  72
+
  73
+	static inline function isSingular(s:Selector):Bool
  74
+	{
  75
+		return s[s.length - 1].id != null;
  76
+	}	
  77
+	
  78
+	static function getXmlType(t:haxe.macro.Type, s:Selector):Outcome<ExtensionType, Void>
  79
+	{
  80
+		var last = s[s.length - 1];
  81
+		if (last.tag == null)
  82
+			return Failure();
  83
+
  84
+		var p = switch(t)
  85
+		{
  86
+			case TType(ty, p):
  87
+				if (ty.toString() != "selecthxml.TypedXml")
  88
+					return Failure();
  89
+				p[0];
  90
+			default:
  91
+				return Failure();
  92
+		}
  93
+		var pt = switch(p)
  94
+		{
  95
+			case TType(pt, _):
  96
+				pt.get();
  97
+			default:
  98
+				return Failure();
  99
+		}
  100
+		switch(pt.type)
  101
+		{
  102
+			case TAnonymous(ano):
  103
+				for (field in ano.get().fields)
  104
+				{
  105
+					var args = getMetaArgs(field.meta, 'pseudo');
  106
+					if (args.length == 3 && last.tag.toLowerCase() == args[0].toLowerCase())
  107
+					{
  108
+						for (i in last.attrs)
  109
+						{
  110
+							if (i.name.toLowerCase() != args[1].toLowerCase())
  111
+								continue;
  112
+							switch(i.operator)
  113
+							{
  114
+								case Exactly:
  115
+									if (i.value.toLowerCase() == args[2].toLowerCase())
  116
+										return Success(makeXmlTypeReturn(pt.pack, pt.name, field, p));
  117
+								default:
  118
+							}
  119
+						}
  120
+					}
  121
+					var name = field.name;
  122
+					var args = getMetaArgs(field.meta, 'value');
  123
+					if (args.length > 0)
  124
+						name = args[0];
  125
+						
  126
+					if (name.toLowerCase() == last.tag.toLowerCase())
  127
+						return Success(makeXmlTypeReturn(pt.pack, pt.name, field, p));
  128
+				}
  129
+			default:
  130
+		}
  131
+		return Failure();
  132
+	}
  133
+	
  134
+	static inline function makeXmlTypeReturn(pack:Array<String>, baseName:String, field:ClassField, baseType:Type)
  135
+	{
  136
+		return {
  137
+			type: Context.follow(field.type),
  138
+			basePack: pack,
  139
+			baseName: baseName,
  140
+			name: field.name.charAt(0).toUpperCase() + field.name.substr(1),
  141
+			baseType: baseType
  142
+		};
  143
+	}
  144
+
  145
+	static function createExtension(base:TypePath, fields:Array<ClassField>, t:ExtensionType)
  146
+	{
  147
+		var tdFields = [];
  148
+
  149
+		for (field in fields)
  150
+		{
  151
+			tdFields.push(makeField(field.name, FProp("get_" +field.name, "set_" +field.name, TypeTools.toComplex(field.type))));
  152
+			tdFields.push(makeField("get_" +field.name,
  153
+				FFun(FunctionTools.func(Context.parse("return " +getValueString(field), Context.currentPos()), null, null, null, false)),
  154
+				true));
  155
+			tdFields.push(makeField("set_" +field.name,
  156
+				FFun(FunctionTools.func(Context.parse("{__x_m_l__.set('" +field.name + "', Std.string(v)); return " +field.name+ ";}", Context.currentPos()), [FunctionTools.toArg("v", field.type.toComplex())], field.type.toComplex(), null, false)),
  157
+				true));
  158
+		}
  159
+
  160
+		var pack = t.basePack.copy();
  161
+		pack.push(t.baseName.charAt(0).toLowerCase() + t.baseName.substr(1));
  162
+		pack.unshift("types");
  163
+		pack.unshift("selecthxml");		
  164
+		
  165
+		base.params = [TPType(TPath( {
  166
+			pack: ["selecthxml"],
  167
+			name: "TypedXml",
  168
+			sub: null,
  169
+			params: [TPType(t.baseType.toComplex())]
  170
+		}))];
  171
+
  172
+		var td = {
  173
+			name: t.name,
  174
+			pack: pack,
  175
+			pos: Context.currentPos(),
  176
+			meta: [],
  177
+			params: [],
  178
+			isExtern: false,
  179
+			kind: TDClass(base),
  180
+			fields: tdFields
  181
+		};
  182
+
  183
+		Context.defineType(td);
  184
+
  185
+		return TPath( {
  186
+			name: t.name,
  187
+			pack: pack,
  188
+			params: [],
  189
+			sub: null
  190
+		});		
  191
+	}
  192
+	
  193
+	static function makeField(name, kind, priv = false)
  194
+	{
  195
+		return {
  196
+			name:name,
  197
+			kind:kind,
  198
+			pos:Context.currentPos(),
  199
+			access:[priv ? APrivate : APublic],
  200
+			meta:[],
  201
+			doc:null
  202
+		};
  203
+	}
  204
+	
  205
+	static function getValueString(n:ClassField)
  206
+	{
  207
+		var name = n.name;
  208
+		var args = getMetaArgs(n.meta, 'value');
  209
+		if (args.length > 0)
  210
+			name = args[0];
  211
+			
  212
+		var ret = "__x_m_l__.get('" +name + "')";
  213
+		
  214
+		switch(n.type)
  215
+		{
  216
+			case TInst(t, p):
  217
+				switch(t.toString())
  218
+				{
  219
+					case "Int":
  220
+						return "Std.parseInt(" + ret + ")";
  221
+					case "Float":
  222
+						return "Std.parseFloat(" + ret + ")";
  223
+				}
  224
+			default:
  225
+		}
  226
+		return ret;
  227
+	}
  228
+	
  229
+	static function getMetaArgs(metadata:MetaAccess, name:String)
  230
+	{
  231
+		if (!metadata.has(name)) return [];
  232
+		
  233
+		var ret = [];
  234
+		for (meta in metadata.get())
  235
+		{
  236
+			if (meta.name == name)
  237
+			{
  238
+				for (param in meta.params)
  239
+				{
  240
+					switch(param.getName())
  241
+					{
  242
+						case Success(s):
  243
+							ret.push(s);
  244
+						default:
  245
+					}
  246
+				}
  247
+			}
  248
+		}
  249
+		return ret;
  250
+	}
  251
+	
  252
+	#end
  253
+}
  254
+
  255
+typedef ExtensionType = { type:Type, basePack:Array<String>, baseName:String, name:String, baseType:Type };
350  src/selecthxml/engine/Parser.hx
... ...
@@ -0,0 +1,350 @@
  1
+package selecthxml.engine;
  2
+import selecthxml.engine.Type;
  3
+
  4
+class Parser {
  5
+	var lexer:SelectorLexer;
  6
+	var buffer:Array<Token>;
  7
+
  8
+	public function new(lexer:SelectorLexer) {
  9
+		this.lexer = lexer;
  10
+		buffer = new Array<Token>();
  11
+	}
  12
+	
  13
+	public function parse():Selector {
  14
+		var parts = [];
  15
+		while (true) {
  16
+			skipWhitespace();
  17
+			var s = parseSimple();
  18
+			parts.push(s);
  19
+			skipWhitespace();		
  20
+			var t = readToken();			
  21
+			switch(t.def) {
  22
+				case TGt:    s.combinator = Child;
  23
+				case TPlus:  s.combinator = AdjacentSibling;
  24
+				case TTilde: s.combinator = GeneralSibling;					
  25
+				case TEof:
  26
+					break;
  27
+				default:
  28
+					pushToken(t);
  29
+					s.combinator = Descendant;
  30
+			}
  31
+		}
  32
+		return parts;
  33
+	}
  34
+	
  35
+	public function parseSimple():SelectorPart {
  36
+		var part = {
  37
+			universal: false,
  38
+			id: null,
  39
+			tag: null,
  40
+			classes: [],
  41
+			attrs: [],
  42
+			pseudos: [],
  43
+			combinator: null
  44
+		}
  45
+		var failed = true;
  46
+		while(true) {
  47
+			var t = readToken();
  48
+			switch(t.def) {
  49
+				case TAsterisk:
  50
+					if (part.universal)
  51
+						// TODO: Proper selector order
  52
+						throw EAlreadyUniversal(t);
  53
+					part.universal = true;
  54
+					failed = false;					
  55
+				// tag selector (div)
  56
+				case TAlpha(s):
  57
+					pushToken(t);
  58
+					part.tag = parseIdent();
  59
+					failed = false;				
  60
+				// id selector (#id)
  61
+				case THash:
  62
+					var name = parseNmChar();
  63
+					if (name.length == 0)
  64
+						unexpected(readToken());
  65
+					part.id = name;
  66
+					failed = false;
  67
+				// class selector (.class)
  68
+				case TDot:
  69
+					part.classes.push(parseIdent());
  70
+					failed = false;
  71
+				// pseudo selector (:psuedo)
  72
+				case TColon:
  73
+					t = readToken();
  74
+					switch(t.def) {
  75
+						// Support double colon ::
  76
+						case TColon:							
  77
+						default: pushToken(t);
  78
+					}					
  79
+					var ident = parseIdent();
  80
+					var ps = null;
  81
+					switch(ident) {
  82
+						case "root":            ps = PsRoot;
  83
+						case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type": 
  84
+							expect(TParenOpen);
  85
+							var nth = parseNth();
  86
+							skipWhitespace();
  87
+							expect(TParenClose);
  88
+							switch(ident) {
  89
+								case "nth-child":        ps = PsNthChild(nth.a, nth.b);
  90
+								case "nth-last-child":   ps = PsNthLastChild(nth.a, nth.b);
  91
+								case "nth-of-type":      ps = PsNthOfType(nth.a, nth.b);
  92
+								case "nth-last-of-type": ps = PsNthLastOfType(nth.a, nth.b);
  93
+							}
  94
+						case "first-child":      ps = PsFirstChild;
  95
+						case "last-child":       ps = PsLastChild;
  96
+						case "first-of-type":    ps = PsFirstOfType;
  97
+						case "last-of-type":     ps = PsLastOfType;
  98
+						case "only-child":       ps = PsOnlyChild;
  99
+						case "only-of-type":     ps = PsOnlyOfType;
  100
+						case "empty":            ps = PsEmpty;
  101
+						case "link":             ps = PsLink;
  102
+						case "visited":          ps = PsVisited;
  103
+						case "active":           ps = PsActive;
  104
+						case "hover":            ps = PsHover;
  105
+						case "focus":            ps = PsFocus;
  106
+						case "target":           ps = PsTarget;
  107
+						case "lang(fr)":     
  108
+							expect(TParenOpen);
  109
+							skipWhitespace();
  110
+							ident = parseIdent();
  111
+							skipWhitespace();
  112
+							expect(TParenClose);
  113
+							ps = PsLang(ident);								
  114
+						case "enabled":          ps = PsEnabled;
  115
+						case "disabled":         ps = PsDisabled;
  116
+						case "checked":          ps = PsChecked;
  117
+						case "first-line":      ps = PsFirstLine;
  118
+						case "first-letter":    ps = PsFirstLetter;
  119
+						case "before":          ps = PsBefore;
  120
+						case "after":           ps = PsAfter;
  121
+						case "not":
  122
+							expect(TParenOpen);
  123
+							var simple = parseSimple();
  124
+							expect(TParenClose);
  125
+							ps = PsNot(simple);								
  126
+						default:
  127
+							var min = t.pos.min;
  128
+							throw EInvalidPseudo(ident, {min: min, max: min + ident.length});
  129
+					}
  130
+					part.pseudos.push(ps);
  131
+					failed = false;
  132
+
  133
+				// Parse attr selector ([attr=value])
  134
+				case TSquareBrkOpen:
  135
+					skipWhitespace();
  136
+					// Read name
  137
+					var name = parseIdent();
  138
+					skipWhitespace();
  139
+					// Read operator (potentially)
  140
+					t = readToken();
  141
+					var op = null;
  142
+					switch(t.def) {
  143
+						case TTilde:    expect(TEquals); op = WhitespaceSeperated; // ~=
  144
+						case TPipe:     expect(TEquals); op = HyphenSeparated; // |=
  145
+						case TCaret:    expect(TEquals); op = BeginsWith; // ^=
  146
+						case TDollar:   expect(TEquals); op = EndsWith; // $=									
  147
+						case TAsterisk: expect(TEquals); op = Contains; // *=
  148
+						case TEquals:   op = Exactly; 
  149
+						case TSquareBrkClose:
  150
+							part.attrs.push( { name: name, value: null, operator: None } );
  151
+							failed = false;
  152
+							continue;
  153
+						default: unexpected(t);
  154
+					}
  155
+					skipWhitespace();
  156
+					// Read value
  157
+					t = readToken();
  158
+					var value = "";
  159
+					switch(t.def) {
  160
+						case TString(s): 
  161
+							value = s;
  162
+						default: 
  163
+							pushToken(t);
  164
+							value = parseIdent();
  165
+					}
  166
+					skipWhitespace();
  167
+					expect(TSquareBrkClose);
  168
+					part.attrs.push({ name: name, value: value, operator: op });
  169
+					failed = false;
  170
+				default: 
  171
+					pushToken(t);
  172
+					break;
  173
+			}
  174
+		}
  175
+		if (failed)
  176
+			throw EExpectedSelector(readToken());
  177
+		return part;
  178
+	}	
  179
+	
  180
+	function parseNth() {
  181
+		skipWhitespace();
  182
+		var t = readToken();
  183
+		switch(t.def) {
  184
+			case TAlpha(s):
  185
+				switch(s) {
  186
+					case "odd":  
  187
+						// :nth-child(odd)
  188
+						return { a: 2, b: 1 };
  189
+					case "even": 
  190
+						// :nth-child(even)
  191
+						return { a: 2, b: 0 };
  192
+					case "n": 
  193
+						pushToken(t);
  194
+						// :nth-child(n+2)
  195
+						return parseNthNext(1);
  196
+					default: 
  197
+						unexpected(t);
  198
+				}			
  199
+			case TPlus, TMinus:
  200
+				var sign = Type.enumEq(t.def, TPlus) ? 1 : -1;
  201
+				t = readToken();
  202
+				switch(t.def) {
  203
+					case TAlpha(s):
  204
+						if (s != "n")
  205
+							unexpected(t);
  206
+						pushToken(t);
  207
+						// :nth-child(-n)
  208
+						return parseNthNext(sign < 0 ? -1 : 0);
  209
+					case TInteger(v):
  210
+						// :nth-child(-2n)
  211
+						return parseNthNext(v * sign);
  212
+					default: 
  213
+						unexpected(t);
  214
+				}
  215
+			case TInteger(v):
  216
+				// :nth-child(2n)
  217
+				return parseNthNext(v);				
  218
+			default:
  219
+				unexpected(t);
  220
+		}
  221
+		return null;
  222
+	}
  223
+	
  224
+	function parseNthNext(a:Int) {
  225
+		skipWhitespace();
  226
+		var t = readToken();
  227
+		switch(t.def) {
  228
+			case TParenClose:
  229
+				pushToken(t);
  230
+				// :nth-child(-2)
  231
+				return { a:0, b: a };				
  232
+			case TAlpha(s):
  233
+				if (s != "n")
  234
+					unexpected(t);	
  235
+				skipWhitespace();
  236
+				t = readToken();
  237
+				switch(t.def) {
  238
+					case TMinus, TPlus:
  239
+						pushToken(t);
  240
+						// :nth-child(2n+2)
  241
+						return { a:a, b:parseInteger(true) };
  242
+					case TInteger(v):
  243
+						pushToken(t);
  244
+						// :nth-child(2n+2)
  245
+						return { a:a, b:parseInteger(true) };							
  246
+					case TParenClose:
  247
+						pushToken(t);
  248
+						// :nth-child(-2n)
  249
+						return { a:a, b:0 }					
  250
+					default: 
  251
+						unexpected(t);
  252
+				}		
  253
+			default: 
  254
+				unexpected(t);
  255
+		}
  256
+		return null;
  257
+	}
  258
+	
  259
+	function parseInteger(allowWhitespace:Bool):Null<Int> {
  260
+		var t = readToken();
  261
+		var sign = 1;
  262
+		switch(t.def) {
  263
+			case TMinus: 
  264
+				sign = -1; 
  265
+				if(allowWhitespace) skipWhitespace();
  266
+				t = readToken();				
  267
+			case TPlus: 
  268
+				sign = 1; 
  269
+				if(allowWhitespace) skipWhitespace();
  270
+				t = readToken();				
  271
+			default:
  272
+		}
  273
+		switch(t.def) {
  274
+			case TInteger(v): return v * sign;				
  275
+			default: unexpected(t);
  276
+		}
  277
+		return null;
  278
+	}
  279
+	
  280
+	function parseIdent():String {
  281
+		// (-?(?:[a-zA-Z]|_)(?:[a-zA-Z0-9]|-|_)*)
  282
+		var str = "";
  283
+		var t = readToken();
  284
+		switch(t.def) {
  285
+			case TMinus:      
  286
+				str += "-";
  287
+				t = readToken();
  288
+				switch (t.def) {
  289
+					case TAlpha(s):   str += s;
  290
+					case TUnderscore: str += "_";
  291
+					default: unexpected(t);
  292
+				}				
  293
+			case TAlpha(s):   str += s;
  294
+			case TUnderscore: str += "_";
  295
+			default: unexpected(t);				
  296
+		}
  297
+		str += parseNmChar();
  298
+		return str;
  299
+	}
  300
+	
  301
+	function parseNmChar():String {
  302
+		// [_a-z0-9-]|{nonascii}|{escape}
  303
+		var str = "";
  304
+		while (true) {
  305
+			var t = readToken();
  306
+			switch (t.def) {					
  307
+				case TAlpha(s):   str += s;
  308
+				case TInteger(v): str += v;
  309
+				case TMinus:      str += "-";
  310
+				case TUnderscore: str += "_";					
  311
+				default: 
  312
+					pushToken(t);
  313
+					break;
  314
+			}		
  315
+		}
  316
+		return str;
  317
+	}
  318
+	
  319
+	function skipWhitespace():Void {
  320
+		while (true) {
  321
+			var t = readToken();
  322
+			switch(t.def) {
  323
+				case TWhitespace:
  324
+				default:
  325
+					pushToken(t);
  326
+					return;				
  327
+			}
  328
+		}
  329
+	}
  330
+	
  331
+	function readToken():Token {
  332
+		if (buffer.length > 0)
  333
+			return buffer.shift();
  334
+		return lexer.readToken();
  335
+	}
  336
+	
  337
+	function pushToken(t:Token):Void {
  338
+		buffer.push(t);
  339
+	}
  340
+	
  341
+	function unexpected(t:Token):Void {
  342
+		throw EUnexpectedToken(t);
  343
+	}
  344
+	
  345
+	function expect(t:TokenDef):Void {
  346
+		var tk = readToken();
  347
+		if (!Type.enumEq(tk.def, t))
  348
+			throw EExpected(t, tk);
  349
+	}	
  350
+}
71  src/selecthxml/engine/RegexLexer.hx
... ...
@@ -0,0 +1,71 @@
  1
+package selecthxml.engine;
  2
+import selecthxml.engine.Type;
  3
+
  4
+class RegexLexer {
  5
+	/*
  6
+	 * alpha      ([a-zA-Z]+)
  7
+	 * string1    "((?:[\t !#$%&(-~]|\\")*)"
  8
+	 * string2    '((?:[\t !#$%&(-~]|\\')*)'
  9
+	 * num        ([0-9]+)
  10
+	 * chars      ([.#:*[\]()>+-\\~^$|=_])
  11
+	 * whitespace (\s+)
  12
+	 * re ^(?:{alpha}|{num}|{string1}|{string2}|{chars}|{whitespace})
  13
+	 */
  14
+	static var re:EReg = ~/^(?:([a-zA-Z]+)|([0-9]+)|"((?:[\t !#$%&(-~]|\\")*)"|'((?:[\t !#$%&(-~]|\\')*)'|([.#:*[\]()>+-\\~^$|=_])|(\s+))/;
  15
+
  16
+	var s:String;
  17
+	var lastMin:Int;
  18
+	var inputLength:Int;
  19
+	
  20
+	public function new(input:String) {
  21
+		s = input;
  22
+		lastMin = 0;
  23
+		inputLength = input.length;
  24
+	}
  25
+	
  26
+	public function readToken():Token {
  27
+		var token = readTokenDef();
  28
+		var min = lastMin;
  29
+		var max = inputLength - s.length;
  30
+		lastMin = max;		
  31
+		return { def: token, pos: {min: min, max: max} }
  32
+	}
  33
+	
  34
+	public function readTokenDef():TokenDef {
  35
+		if (!re.match(s)) {
  36
+			if (s.length == 0)
  37
+				return TEof;
  38
+			var startPos = inputLength - s.length;
  39
+			throw EInvalidCharacter(s.charAt(0), { min:startPos, max:startPos + 1 } );
  40
+		}		
  41
+		s = re.matchedRight();
  42
+		
  43
+		if (re.matched(1) != null)      return TAlpha(re.matched(1));
  44
+		else if (re.matched(2) != null) return TInteger(Std.parseInt(re.matched(2)));
  45
+		else if (re.matched(3) != null) return TString(re.matched(3));
  46
+		else if (re.matched(4) != null) return TString(re.matched(4));
  47
+		else if (re.matched(5) != null) {
  48
+			switch(re.matched(5)) {
  49
+				case ".":  return TDot;
  50
+				case "#":  return THash;
  51
+				case ":":  return TColon;
  52
+				case "*":  return TAsterisk;
  53
+				case "[":  return TSquareBrkOpen;
  54
+				case "]":  return TSquareBrkClose;
  55
+				case "(":  return TParenOpen;
  56
+				case ")":  return TParenClose;
  57
+				case ">":  return TGt;
  58
+				case "+":  return TPlus;
  59
+				case "-":  return TMinus;
  60
+				case "~":  return TTilde;
  61
+				case "^":  return TCaret;
  62
+				case "$":  return TDollar;
  63
+				case "|":  return TPipe;
  64
+				case "=":  return TEquals;
  65
+				case "_":  return TUnderscore;
  66
+			}
  67
+		}
  68
+		else if (re.matched(6) != null) return TWhitespace;
  69
+		return null;
  70
+	}	
  71
+}
295  src/selecthxml/engine/SelectEngine.hx
... ...
@@ -0,0 +1,295 @@
  1
+package selecthxml.engine;
  2
+import selecthxml.engine.Type;
  3
+
  4
+class SelectEngine {
  5
+	static inline var ELEMENT_NODE = 1;
  6
+	
  7
+	public function new() {}
  8
+	
  9
+	public function query(selector:Selector, root:SelectableDom):Array<SelectableDom> {
  10
+
  11
+		var candidates = getCandidates(selector, root);		
  12
+		// TODO: Make this readable
  13
+		var results = [];		
  14
+		for (i in candidates) {			
  15
+			var ctx = i;
  16
+			var failed = false;
  17
+			for(j in 1 ... selector.length) {
  18
+				var part = selector[selector.length - j - 1];
  19
+				// Handle combinators
  20
+				switch(part.combinator) {
  21
+					case Descendant: 
  22
+						var found = false;
  23
+						while (true) {
  24
+							ctx = ctx.parentNode;
  25
+							if (ctx == null || ctx.nodeType != ELEMENT_NODE) {
  26
+								// Reached top of DOM tree
  27
+								failed = true;
  28
+								break;
  29
+							}
  30
+							if(matches(part, ctx))
  31
+								break;
  32
+						}
  33
+					case Child:
  34
+						ctx = ctx.parentNode;
  35
+						if (ctx == null || ctx.nodeType != ELEMENT_NODE || !matches(part, ctx))
  36
+							failed = true;
  37
+					case AdjacentSibling:						
  38
+						ctx = previousSiblingElement(ctx);
  39
+						if (ctx == null || !matches(part, ctx))  {
  40
+							failed = true;
  41
+							break;
  42
+						}
  43
+					case GeneralSibling:
  44
+						while (true) {							
  45
+							ctx = previousSiblingElement(ctx);
  46
+							if (ctx == null) {
  47
+								failed = true;
  48
+								break;
  49
+							}
  50
+							if (matches(part, ctx))
  51
+								break;
  52
+						}
  53
+				}
  54
+				if (failed) 
  55
+					break;
  56
+			}
  57
+			if (!failed)
  58
+				results.push(i);
  59
+		}		
  60
+		return results;
  61
+	}
  62
+	
  63
+	function getCandidates(selector:Selector, root:SelectableDom):Array<SelectableDom> {
  64
+		var p = selector[selector.length-1];
  65
+		var candidates = [];
  66
+		// Look for candidates using the most efficent methods available
  67
+		if (p.id != null) {
  68
+			var el = untyped root.getElementById(p.id);
  69
+			if (el != null && matches(p, el))
  70
+				candidates.push(el);
  71
+		}
  72
+		else if (p.classes.length > 0 && untyped root.getElementsByClassName != null) {
  73
+			var names = p.classes.join(" ");
  74
+			var list:Array<SelectableDom> = untyped root.getElementsByClassName(names);
  75
+			for (i in 0 ... list.length)
  76
+				if(matches(p, list[i]))
  77
+					candidates.push(list[i]);
  78
+		}
  79
+		else if (p.tag != null) {
  80
+			var list = root.getElementsByTagName(p.tag);
  81
+			for (i in 0 ... list.length)
  82
+				if(matches(p, list[i]))
  83
+					candidates.push(list[i]);
  84
+		}
  85
+		else {
  86
+			var list = root.getElementsByTagName("*");
  87
+			for (i in 0 ... list.length)
  88
+				if(matches(p, list[i]))
  89
+					candidates.push(list[i]);
  90
+		}
  91
+		return candidates;
  92
+	}
  93
+
  94
+	function matches(part:SelectorPart, el:SelectableDom):Bool {
  95
+		if (part.id != null) {
  96
+			if (el.getAttribute("id") != part.id) 
  97
+				return false;
  98
+		}		
  99
+		if (part.tag != null) {
  100
+			if (el.nodeName.toLowerCase() != part.tag.toLowerCase())
  101
+				return false;
  102
+		}		
  103
+		if (part.classes.length > 0) {
  104
+			var c = el.className.split(" ");
  105
+			for(className in part.classes) 
  106
+				if(!Lambda.has(c, className))
  107
+					return false;
  108
+		}
  109
+		if (part.attrs.length > 0) {
  110
+			for (attr in part.attrs) {
  111
+				var value = el.getAttribute(attr.name);
  112
+				if (value == null)
  113
+					return false;
  114
+				switch(attr.operator) {
  115
+					case None:
  116
+					case Exactly: 
  117
+						if (value != attr.value)
  118
+							return false;
  119
+					case WhitespaceSeperated:
  120
+						var c = value.split(" ");
  121