Skip to content

Commit 01e6c7b

Browse files
committed
feat(Parser): implement Parser
Add a simple parser implementation that supports only field reads.
1 parent acd7035 commit 01e6c7b

File tree

11 files changed

+306
-75
lines changed

11 files changed

+306
-75
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export class AST {
2+
eval(context) {
3+
}
4+
5+
visit(visitor) {
6+
}
7+
}
8+
9+
export class ImplicitReceiver extends AST {
10+
eval(context) {
11+
return context;
12+
}
13+
14+
visit(visitor) {
15+
visitor.visitImplicitReceiver(this);
16+
}
17+
}
18+
19+
export class FieldRead extends AST {
20+
constructor(receiver:AST, name:string, getter:Function) {
21+
this.receiver = receiver;
22+
this.name = name;
23+
this.getter = getter;
24+
}
25+
26+
eval(context) {
27+
return this.getter(this.receiver.eval(context));
28+
}
29+
30+
visit(visitor) {
31+
visitor.visitFieldRead(this);
32+
}
33+
}
34+
35+
//INTERFACE
36+
export class AstVisitor {
37+
visitImplicitReceiver(ast:ImplicitReceiver) {}
38+
visitFieldRead(ast:FieldRead) {}
39+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'dart:mirrors';
2+
3+
class ClosureMap {
4+
Function getter(String name) {
5+
var symbol = new Symbol(name);
6+
return (receiver) => reflect(receiver).getField(symbol).reflectee;
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class ClosureMap {
2+
getter(name:string) {
3+
return new Function('o', 'return o.' + name + ';');
4+
}
5+
}

modules/change_detection/src/parser/scanner.js renamed to modules/change_detection/src/parser/lexer.js

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import {List, ListWrapper, SetWrapper} from "facade/collection";
22
import {int, FIELD, NumberWrapper, StringJoiner, StringWrapper} from "facade/lang";
33

4-
// TODO(chirayu): Rewrite as consts when possible.
5-
export var TOKEN_TYPE_CHARACTER = 1;
6-
export var TOKEN_TYPE_IDENTIFIER = 2;
7-
export var TOKEN_TYPE_KEYWORD = 3;
8-
export var TOKEN_TYPE_STRING = 4;
9-
export var TOKEN_TYPE_OPERATOR = 5;
10-
export var TOKEN_TYPE_NUMBER = 6;
4+
export const TOKEN_TYPE_CHARACTER = 1;
5+
export const TOKEN_TYPE_IDENTIFIER = 2;
6+
export const TOKEN_TYPE_KEYWORD = 3;
7+
export const TOKEN_TYPE_STRING = 4;
8+
export const TOKEN_TYPE_OPERATOR = 5;
9+
export const TOKEN_TYPE_NUMBER = 6;
10+
11+
export class Lexer {
12+
tokenize(text:string):List {
13+
var scanner = new _Scanner(text);
14+
var tokens = [];
15+
var token = scanner.scanToken();
16+
while (token != null) {
17+
ListWrapper.push(tokens, token);
18+
token = scanner.scanToken();
19+
}
20+
return tokens;
21+
}
22+
}
1123

1224
export class Token {
1325
@FIELD('final index:int')
@@ -107,35 +119,35 @@ function newNumberToken(index:int, n:number):Token {
107119
}
108120

109121

110-
var EOF:Token = new Token(-1, 0, 0, "");
111-
112-
const $EOF = 0;
113-
const $TAB = 9;
114-
const $LF = 10;
115-
const $VTAB = 11;
116-
const $FF = 12;
117-
const $CR = 13;
118-
const $SPACE = 32;
119-
const $BANG = 33;
120-
const $DQ = 34;
121-
const $$ = 36;
122-
const $PERCENT = 37;
123-
const $AMPERSAND = 38;
124-
const $SQ = 39;
125-
const $LPAREN = 40;
126-
const $RPAREN = 41;
127-
const $STAR = 42;
128-
const $PLUS = 43;
129-
const $COMMA = 44;
130-
const $MINUS = 45;
131-
const $PERIOD = 46;
132-
const $SLASH = 47;
133-
const $COLON = 58;
134-
const $SEMICOLON = 59;
135-
const $LT = 60;
136-
const $EQ = 61;
137-
const $GT = 62;
138-
const $QUESTION = 63;
122+
export var EOF:Token = new Token(-1, 0, 0, "");
123+
124+
export const $EOF = 0;
125+
export const $TAB = 9;
126+
export const $LF = 10;
127+
export const $VTAB = 11;
128+
export const $FF = 12;
129+
export const $CR = 13;
130+
export const $SPACE = 32;
131+
export const $BANG = 33;
132+
export const $DQ = 34;
133+
export const $$ = 36;
134+
export const $PERCENT = 37;
135+
export const $AMPERSAND = 38;
136+
export const $SQ = 39;
137+
export const $LPAREN = 40;
138+
export const $RPAREN = 41;
139+
export const $STAR = 42;
140+
export const $PLUS = 43;
141+
export const $COMMA = 44;
142+
export const $MINUS = 45;
143+
export const $PERIOD = 46;
144+
export const $SLASH = 47;
145+
export const $COLON = 58;
146+
export const $SEMICOLON = 59;
147+
export const $LT = 60;
148+
export const $EQ = 61;
149+
export const $GT = 62;
150+
export const $QUESTION = 63;
139151

140152
const $0 = 48;
141153
const $9 = 57;
@@ -173,7 +185,7 @@ export class ScannerError extends Error {
173185
}
174186
}
175187

176-
export class Scanner {
188+
class _Scanner {
177189
@FIELD('final input:String')
178190
@FIELD('final length:int')
179191
@FIELD('peek:int')
@@ -191,7 +203,6 @@ export class Scanner {
191203
this.peek = ++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
192204
}
193205

194-
195206
scanToken():Token {
196207
var input = this.input,
197208
length = this.length,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,81 @@
1+
import {FIELD, int} from 'facade/lang';
2+
import {ListWrapper, List} from 'facade/collection';
3+
import {Lexer, EOF, Token, $PERIOD} from './lexer';
4+
import {ClosureMap} from './closure_map';
5+
import {AST, ImplicitReceiver, FieldRead} from './ast';
6+
7+
var _implicitReceiver = new ImplicitReceiver();
8+
19
export class Parser {
10+
@FIELD('final _lexer:Lexer')
11+
@FIELD('final _closureMap:ClosureMap')
12+
constructor(lexer:Lexer, closureMap:ClosureMap){
13+
this._lexer = lexer;
14+
this._closureMap = closureMap;
15+
}
16+
17+
parse(input:string):AST {
18+
var tokens = this._lexer.tokenize(input);
19+
return new _ParseAST(tokens, this._closureMap).parseChain();
20+
}
221
}
22+
23+
class _ParseAST {
24+
@FIELD('final tokens:List<Token>')
25+
@FIELD('final closureMap:ClosureMap')
26+
@FIELD('index:int')
27+
constructor(tokens:List, closureMap:ClosureMap) {
28+
this.tokens = tokens;
29+
this.index = 0;
30+
this.closureMap = closureMap;
31+
}
32+
33+
peek(offset:int):Token {
34+
var i = this.index + offset;
35+
return i < this.tokens.length ? this.tokens[i] : EOF;
36+
}
37+
38+
get next():Token {
39+
return this.peek(0);
40+
}
41+
42+
advance() {
43+
this.index ++;
44+
}
45+
46+
optionalCharacter(code:int):boolean {
47+
if (this.next.isCharacter(code)) {
48+
this.advance();
49+
return true;
50+
} else {
51+
return false;
52+
}
53+
}
54+
55+
parseChain():AST {
56+
var exprs = [];
57+
while (this.index < this.tokens.length) {
58+
ListWrapper.push(exprs, this.parseAccess());
59+
}
60+
return ListWrapper.first(exprs);
61+
}
62+
63+
parseAccess():AST {
64+
var result = this.parseFieldRead(_implicitReceiver);
65+
while(this.optionalCharacter($PERIOD)) {
66+
result = this.parseFieldRead(result);
67+
}
68+
return result;
69+
}
70+
71+
parseFieldRead(receiver):AST {
72+
var id = this.parseIdentifier();
73+
return new FieldRead(receiver, id, this.closureMap.getter(id));
74+
}
75+
76+
parseIdentifier():string {
77+
var n = this.next;
78+
this.advance();
79+
return n.toString();
80+
}
81+
}

modules/change_detection/src/watch_group.js

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,42 @@
11
import {ProtoRecord, Record} from './record';
2-
import {FIELD} from 'facade/lang';
3-
import {ListWrapper} from 'facade/collection';
2+
import {FIELD, IMPLEMENTS, isBlank, isPresent} from 'facade/lang';
3+
import {AST, FieldRead, ImplicitReceiver, AstVisitor} from './parser/ast';
44

55
export class ProtoWatchGroup {
6-
@FIELD('final headRecord:ProtoRecord')
7-
@FIELD('final tailRecord:ProtoRecord')
6+
@FIELD('headRecord:ProtoRecord')
7+
@FIELD('tailRecord:ProtoRecord')
88
constructor() {
99
this.headRecord = null;
1010
this.tailRecord = null;
1111
}
1212

1313
/**
14-
* Parses [expression] into [ProtoRecord]s and adds them to [ProtoWatchGroup].
14+
* Parses [ast] into [ProtoRecord]s and adds them to [ProtoWatchGroup].
1515
*
16-
* @param expression The expression to watch
16+
* @param ast The expression to watch
1717
* @param memento an opaque object which will be passed to WatchGroupDispatcher on
1818
* detecting a change.
1919
* @param shallow Should collections be shallow watched
2020
*/
21-
watch(expression:string,
21+
watch(ast:AST,
2222
memento,
2323
shallow = false)
2424
{
25-
var parts = expression.split('.');
26-
var protoRecords = ListWrapper.createFixedSize(parts.length);
25+
var creator = new ProtoRecordCreator(this);
26+
creator.createRecordsFromAST(ast, memento);
27+
this._addRecords(creator.headRecord, creator.tailRecord);
28+
}
2729

28-
for (var i = parts.length - 1; i >= 0; i--) {
29-
protoRecords[i] = new ProtoRecord(this, parts[i], memento);
30-
memento = null;
31-
}
32-
33-
for (var i = 0; i < parts.length; i++) {
34-
var protoRecord = protoRecords[i];
35-
if (this.headRecord === null) {
36-
this.headRecord = this.tailRecord = protoRecord;
37-
} else {
38-
this.tailRecord.next = protoRecord;
39-
protoRecord.prev = this.tailRecord;
40-
this.tailRecord = protoRecord;
41-
}
30+
// try to encapsulate this behavior in some class (e.g., LinkedList)
31+
// so we can say: group.appendList(creator.list);
32+
_addRecords(head:ProtoRecord, tail:ProtoRecord) {
33+
if (isBlank(this.headRecord)) {
34+
this.headRecord = head;
35+
} else {
36+
this.tailRecord.next = head;
37+
head.prev = this.tailRecord;
4238
}
39+
this.tailRecord = tail;
4340
}
4441

4542
instantiate(dispatcher:WatchGroupDispatcher):WatchGroup {
@@ -109,3 +106,41 @@ export class WatchGroupDispatcher {
109106
// The record holds the previous value at the time of the call
110107
onRecordChange(record:Record, context) {}
111108
}
109+
110+
@IMPLEMENTS(AstVisitor)
111+
class ProtoRecordCreator {
112+
@FIELD('final protoWatchGroup:ProtoWatchGroup')
113+
@FIELD('headRecord:ProtoRecord')
114+
@FIELD('tailRecord:ProtoRecord')
115+
constructor(protoWatchGroup) {
116+
this.protoWatchGroup = protoWatchGroup;
117+
this.headRecord = null;
118+
this.tailRecord = null;
119+
}
120+
121+
visitImplicitReceiver(ast:ImplicitReceiver) {
122+
//do nothing
123+
}
124+
125+
visitFieldRead(ast:FieldRead) {
126+
ast.receiver.visit(this);
127+
this.add(new ProtoRecord(this.protoWatchGroup, ast.name, null));
128+
}
129+
130+
createRecordsFromAST(ast:AST, memento){
131+
ast.visit(this);
132+
if (isPresent(this.tailRecord)) {
133+
this.tailRecord.dispatchMemento = memento;
134+
}
135+
}
136+
137+
add(protoRecord:ProtoRecord) {
138+
if (this.headRecord === null) {
139+
this.headRecord = this.tailRecord = protoRecord;
140+
} else {
141+
this.tailRecord.next = protoRecord;
142+
protoRecord.prev = this.tailRecord;
143+
this.tailRecord = protoRecord;
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)