2014-11-11 16:54:15 -08:00
|
|
|
import {ProtoRecord, Record, PROTO_RECORD_CONST, PROTO_RECORD_PURE_FUNCTION,
|
2014-11-14 12:34:35 -08:00
|
|
|
PROTO_RECORD_PROPERTY, PROTO_RECORD_METHOD, PROTO_RECORD_CLOSURE, PROTO_RECORD_FORMATTTER} from './record';
|
2014-11-10 16:11:29 -08:00
|
|
|
import {FIELD, IMPLEMENTS, isBlank, isPresent, int, toBool, autoConvertAdd, BaseException} from 'facade/lang';
|
2014-11-11 17:47:45 -08:00
|
|
|
import {ListWrapper, MapWrapper} from 'facade/collection';
|
2014-11-11 16:54:15 -08:00
|
|
|
import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive,
|
2014-11-11 17:39:40 -08:00
|
|
|
Binary, Formatter, MethodCall, FunctionCall, PrefixNot, Conditional,
|
|
|
|
LiteralArray, LiteralMap, KeyedAccess, Chain, Assignment} from './parser/ast';
|
2014-09-26 11:20:08 -07:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
|
|
|
|
export class ProtoRecordRange {
|
2014-10-28 12:22:38 -04:00
|
|
|
@FIELD('headRecord:ProtoRecord')
|
|
|
|
@FIELD('tailRecord:ProtoRecord')
|
2014-11-14 12:34:35 -08:00
|
|
|
constructor() {
|
2014-10-01 16:29:45 +02:00
|
|
|
this.headRecord = null;
|
|
|
|
this.tailRecord = null;
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-09-30 15:50:20 -07:00
|
|
|
/**
|
2014-11-19 15:52:01 -08:00
|
|
|
* Parses [ast] into [ProtoRecord]s and adds them to [ProtoRecordRange].
|
2014-09-30 15:50:20 -07:00
|
|
|
*
|
2014-10-28 12:22:38 -04:00
|
|
|
* @param ast The expression to watch
|
2014-10-02 15:14:32 +02:00
|
|
|
* @param memento an opaque object which will be passed to WatchGroupDispatcher on
|
2014-09-30 15:50:20 -07:00
|
|
|
* detecting a change.
|
|
|
|
* @param shallow Should collections be shallow watched
|
|
|
|
*/
|
2014-11-19 15:52:01 -08:00
|
|
|
addRecordsFromAST(ast:AST,
|
2014-10-02 15:14:32 +02:00
|
|
|
memento,
|
2014-10-27 17:57:36 +01:00
|
|
|
shallow = false)
|
2014-09-26 11:20:08 -07:00
|
|
|
{
|
2014-10-28 12:22:38 -04:00
|
|
|
var creator = new ProtoRecordCreator(this);
|
|
|
|
creator.createRecordsFromAST(ast, memento);
|
|
|
|
this._addRecords(creator.headRecord, creator.tailRecord);
|
|
|
|
}
|
2014-10-02 15:14:32 +02:00
|
|
|
|
2014-10-28 12:22:38 -04:00
|
|
|
// try to encapsulate this behavior in some class (e.g., LinkedList)
|
|
|
|
// so we can say: group.appendList(creator.list);
|
|
|
|
_addRecords(head:ProtoRecord, tail:ProtoRecord) {
|
|
|
|
if (isBlank(this.headRecord)) {
|
|
|
|
this.headRecord = head;
|
|
|
|
} else {
|
|
|
|
this.tailRecord.next = head;
|
|
|
|
head.prev = this.tailRecord;
|
2014-10-02 15:14:32 +02:00
|
|
|
}
|
2014-10-28 12:22:38 -04:00
|
|
|
this.tailRecord = tail;
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-10-29 15:41:50 -07:00
|
|
|
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
|
|
|
|
// but @Implements is not ready yet.
|
2014-11-19 15:52:01 -08:00
|
|
|
instantiate(dispatcher, formatters:Map):RecordRange {
|
|
|
|
var recordRange:RecordRange = new RecordRange(this, dispatcher);
|
2014-10-02 15:14:32 +02:00
|
|
|
if (this.headRecord !== null) {
|
2014-11-19 15:52:01 -08:00
|
|
|
this._createRecords(recordRange, formatters);
|
2014-11-10 16:11:29 -08:00
|
|
|
this._setDestination();
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
2014-11-19 15:52:01 -08:00
|
|
|
return recordRange;
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
2014-09-30 15:50:20 -07:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
_createRecords(recordRange:RecordRange, formatters:Map) {
|
2014-11-14 15:35:41 -08:00
|
|
|
for (var proto = this.headRecord; proto != null; proto = proto.next) {
|
2014-11-19 15:52:01 -08:00
|
|
|
var record = new Record(recordRange, proto, formatters);
|
2014-11-14 15:35:41 -08:00
|
|
|
proto.recordInConstruction = record;
|
2014-11-19 15:52:01 -08:00
|
|
|
recordRange.addRecord(record);
|
2014-11-10 16:11:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_setDestination() {
|
|
|
|
for (var proto = this.headRecord; proto != null; proto = proto.next) {
|
|
|
|
if (proto.dest instanceof Destination) {
|
|
|
|
proto.recordInConstruction.dest = proto.dest.record.recordInConstruction;
|
|
|
|
}
|
|
|
|
proto.recordInConstruction = null;
|
|
|
|
}
|
|
|
|
}
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
export class RecordRange {
|
|
|
|
@FIELD('final protoRecordRange:ProtoRecordRange')
|
2014-09-19 16:38:37 -07:00
|
|
|
@FIELD('final dispatcher:WatchGroupDispatcher')
|
2014-10-01 16:29:45 +02:00
|
|
|
@FIELD('final headRecord:Record')
|
|
|
|
@FIELD('final tailRecord:Record')
|
2014-11-18 17:26:38 -08:00
|
|
|
@FIELD('final disabled:boolean')
|
2014-10-29 15:41:50 -07:00
|
|
|
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
|
|
|
|
// but @Implements is not ready yet.
|
2014-11-19 15:52:01 -08:00
|
|
|
constructor(protoRecordRange:ProtoRecordRange, dispatcher) {
|
|
|
|
this.protoRecordRange = protoRecordRange;
|
2014-09-26 11:20:08 -07:00
|
|
|
this.dispatcher = dispatcher;
|
2014-10-31 11:49:55 +01:00
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
this.disabled = false;
|
|
|
|
|
|
|
|
this.headRecord = Record.createMarker(this);
|
|
|
|
this.tailRecord = Record.createMarker(this);
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
_glue(this.headRecord, this.tailRecord);
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
/// addRecord assumes that the record is newly created, so it is enabled.
|
2014-11-14 15:35:41 -08:00
|
|
|
addRecord(record:Record) {
|
2014-11-18 17:26:38 -08:00
|
|
|
var lastRecord = this.tailRecord.prev;
|
2014-11-14 15:35:41 -08:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
_glue(lastRecord, record);
|
|
|
|
_glueEnabled(lastRecord, record);
|
|
|
|
_glue(record, this.tailRecord);
|
2014-11-14 15:35:41 -08:00
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
addRange(child:RecordRange) {
|
2014-11-18 17:26:38 -08:00
|
|
|
var lastRecord = this.tailRecord.prev;
|
|
|
|
var lastEnabledRecord = this.findLastEnabledRecord();
|
|
|
|
var firstEnabledChildRecord = child.findFirstEnabledRecord();
|
2014-11-14 15:35:41 -08:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
_glue(lastRecord, child.headRecord);
|
|
|
|
_glue(child.tailRecord, this.tailRecord);
|
2014-11-14 15:35:41 -08:00
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
if (isPresent(lastEnabledRecord) && isPresent(firstEnabledChildRecord)) {
|
2014-11-19 15:52:01 -08:00
|
|
|
_glueEnabled(lastEnabledRecord, firstEnabledChildRecord);
|
2014-11-14 15:35:41 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
removeRange(child:RecordRange) {
|
2014-11-18 17:26:38 -08:00
|
|
|
var firstEnabledChildRecord = child.findFirstEnabledRecord();
|
|
|
|
var lastEnabledChildRecord = child.findLastEnabledRecord();
|
|
|
|
|
|
|
|
var next = child.tailRecord.next;
|
|
|
|
var prev = child.headRecord.prev;
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
_glue(prev, next);
|
2014-11-18 17:26:38 -08:00
|
|
|
|
|
|
|
var nextEnabled = lastEnabledChildRecord.nextEnabled;
|
|
|
|
var prevEnabled = firstEnabledChildRecord.prevEnabled;
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
if (isPresent(nextEnabled)) nextEnabled.prevEnabled = prevEnabled;
|
|
|
|
if (isPresent(prevEnabled)) prevEnabled.nextEnabled = nextEnabled;
|
2014-11-18 17:26:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
findFirstEnabledRecord() {
|
2014-11-19 15:52:01 -08:00
|
|
|
return this._nextEnabledInCurrentRange(this.headRecord);
|
2014-11-18 17:26:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
findLastEnabledRecord() {
|
2014-11-19 15:52:01 -08:00
|
|
|
return this._prevEnabledInCurrentRange(this.tailRecord);
|
2014-11-18 17:26:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
disableRecord(record:Record) {
|
|
|
|
var prevEnabled = record.prevEnabled;
|
|
|
|
var nextEnabled = record.nextEnabled;
|
|
|
|
|
|
|
|
if (isPresent(prevEnabled)) prevEnabled.nextEnabled = nextEnabled;
|
|
|
|
if (isPresent(nextEnabled)) nextEnabled.prevEnabled = prevEnabled;
|
|
|
|
|
|
|
|
record.disabled = true;
|
|
|
|
}
|
|
|
|
|
2014-11-14 15:35:41 -08:00
|
|
|
enableRecord(record:Record) {
|
|
|
|
if (!record.disabled) return;
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
var prevEnabled = this._prevEnabledInCurrentRange(record);
|
|
|
|
var nextEnabled = this._nextEnabledInCurrentRange(record);
|
2014-11-14 15:35:41 -08:00
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
record.prevEnabled = prevEnabled;
|
|
|
|
record.nextEnabled = nextEnabled;
|
|
|
|
|
|
|
|
if (isPresent(prevEnabled)) prevEnabled.nextEnabled = record;
|
|
|
|
if (isPresent(nextEnabled)) nextEnabled.prevEnabled = record;
|
2014-11-14 15:35:41 -08:00
|
|
|
|
|
|
|
record.disabled = false;
|
2014-11-18 17:26:38 -08:00
|
|
|
}
|
2014-11-14 15:35:41 -08:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
disableRange(child:RecordRange) {
|
2014-11-18 17:26:38 -08:00
|
|
|
var firstEnabledChildRecord = child.findFirstEnabledRecord();
|
|
|
|
var lastEnabledChildRecord = child.findLastEnabledRecord();
|
|
|
|
|
|
|
|
var nextEnabled = lastEnabledChildRecord.nextEnabled;
|
|
|
|
var prevEnabled = firstEnabledChildRecord.prevEnabled;
|
|
|
|
|
|
|
|
if (isPresent(nextEnabled)) nextEnabled.prevEnabled = prevEnabled;
|
|
|
|
if (isPresent(prevEnabled)) prevEnabled.nextEnabled = nextEnabled;
|
|
|
|
|
|
|
|
child.disabled = true;
|
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
enableRange(child:RecordRange) {
|
|
|
|
var prevEnabledRecord = this._prevEnabledInCurrentRange(child.headRecord);
|
|
|
|
var nextEnabledRecord = this._nextEnabledInCurrentRange(child.tailRecord);
|
2014-11-18 17:26:38 -08:00
|
|
|
|
|
|
|
var firstEnabledChildRecord = child.findFirstEnabledRecord();
|
|
|
|
var lastEnabledChildRecord = child.findLastEnabledRecord();
|
|
|
|
|
|
|
|
if (isPresent(firstEnabledChildRecord) && isPresent(prevEnabledRecord)){
|
2014-11-19 15:52:01 -08:00
|
|
|
_glueEnabled(prevEnabledRecord, firstEnabledChildRecord);
|
2014-11-14 15:35:41 -08:00
|
|
|
}
|
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
if (isPresent(lastEnabledChildRecord) && isPresent(nextEnabledRecord)){
|
2014-11-19 15:52:01 -08:00
|
|
|
_glueEnabled(lastEnabledChildRecord, nextEnabledRecord);
|
2014-11-14 15:35:41 -08:00
|
|
|
}
|
2014-11-18 17:26:38 -08:00
|
|
|
|
|
|
|
child.disabled = false;
|
2014-11-14 15:35:41 -08:00
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
/// Returns the next enabled record in the current range. If no such record, returns null.
|
|
|
|
_nextEnabledInCurrentRange(record:Record) {
|
|
|
|
if (record === this.tailRecord) return null;
|
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
record = record.next;
|
|
|
|
while (isPresent(record) && record !== this.tailRecord && record.disabled) {
|
2014-11-19 15:52:01 -08:00
|
|
|
if (record.isMarkerRecord && record.recordRange.disabled) {
|
|
|
|
record = record.recordRange.tailRecord.next;
|
2014-11-18 17:26:38 -08:00
|
|
|
} else {
|
|
|
|
record = record.next;
|
|
|
|
}
|
2014-10-31 11:49:55 +01:00
|
|
|
}
|
2014-11-18 17:26:38 -08:00
|
|
|
return record === this.tailRecord ? null : record;
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
/// Returns the prev enabled record in the current range. If no such record, returns null.
|
|
|
|
_prevEnabledInCurrentRange(record:Record) {
|
|
|
|
if (record === this.headRecord) return null;
|
|
|
|
|
2014-11-18 17:26:38 -08:00
|
|
|
record = record.prev;
|
|
|
|
while (isPresent(record) && record !== this.headRecord && record.disabled) {
|
2014-11-19 15:52:01 -08:00
|
|
|
if (record.isMarkerRecord && record.recordRange.disabled) {
|
|
|
|
record = record.recordRange.headRecord.prev;
|
2014-11-18 17:26:38 -08:00
|
|
|
} else {
|
|
|
|
record = record.prev;
|
2014-10-31 11:49:55 +01:00
|
|
|
}
|
|
|
|
}
|
2014-11-18 17:26:38 -08:00
|
|
|
return record === this.headRecord ? null : record;
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
|
|
|
|
2014-09-30 16:39:37 -07:00
|
|
|
/**
|
|
|
|
* Sets the context (the object) on which the change detection expressions will
|
2014-11-19 15:52:01 -08:00
|
|
|
* dereference themselves on. Since the RecordRange can be reused the context
|
|
|
|
* can be re-set many times during the lifetime of the RecordRange.
|
2014-09-30 16:39:37 -07:00
|
|
|
*
|
2014-11-19 15:52:01 -08:00
|
|
|
* @param context the new context for change detection for the current RecordRange
|
2014-09-30 16:39:37 -07:00
|
|
|
*/
|
|
|
|
setContext(context) {
|
2014-10-02 15:14:32 +02:00
|
|
|
for (var record:Record = this.headRecord;
|
|
|
|
record != null;
|
|
|
|
record = record.next) {
|
2014-11-10 16:11:29 -08:00
|
|
|
|
|
|
|
record.updateContext(context);
|
2014-10-02 15:14:32 +02:00
|
|
|
}
|
2014-09-30 16:39:37 -07:00
|
|
|
}
|
2014-09-26 11:20:08 -07:00
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-11-19 15:52:01 -08:00
|
|
|
function _glue(a:Record, b:Record) {
|
|
|
|
a.next = b;
|
|
|
|
b.prev = a;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _glueEnabled(a:Record, b:Record) {
|
|
|
|
a.nextEnabled = b;
|
|
|
|
b.prevEnabled = a;
|
|
|
|
}
|
|
|
|
|
2014-09-28 16:29:11 -07:00
|
|
|
export class WatchGroupDispatcher {
|
2014-10-02 15:14:32 +02:00
|
|
|
// The record holds the previous value at the time of the call
|
2014-09-28 16:29:11 -07:00
|
|
|
onRecordChange(record:Record, context) {}
|
|
|
|
}
|
2014-10-28 12:22:38 -04:00
|
|
|
|
2014-11-10 16:11:29 -08:00
|
|
|
//todo: vsavkin: Create Array and Context destinations?
|
|
|
|
class Destination {
|
|
|
|
constructor(record:ProtoRecord, position:int) {
|
|
|
|
this.record = record;
|
|
|
|
this.position = position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-10-28 12:22:38 -04:00
|
|
|
@IMPLEMENTS(AstVisitor)
|
|
|
|
class ProtoRecordCreator {
|
2014-11-19 15:52:01 -08:00
|
|
|
@FIELD('final protoRecordRange:ProtoRecordRange')
|
2014-10-28 12:22:38 -04:00
|
|
|
@FIELD('headRecord:ProtoRecord')
|
|
|
|
@FIELD('tailRecord:ProtoRecord')
|
2014-11-19 15:52:01 -08:00
|
|
|
constructor(protoRecordRange) {
|
|
|
|
this.protoRecordRange = protoRecordRange;
|
2014-10-28 12:22:38 -04:00
|
|
|
this.headRecord = null;
|
|
|
|
this.tailRecord = null;
|
|
|
|
}
|
|
|
|
|
2014-11-10 16:11:29 -08:00
|
|
|
visitImplicitReceiver(ast:ImplicitReceiver, args) {
|
2014-10-28 12:22:38 -04:00
|
|
|
//do nothing
|
|
|
|
}
|
|
|
|
|
2014-11-10 16:11:29 -08:00
|
|
|
visitLiteralPrimitive(ast:LiteralPrimitive, dest) {
|
|
|
|
this.add(this.construct(PROTO_RECORD_CONST, ast.value, 0, dest));
|
|
|
|
}
|
|
|
|
|
|
|
|
visitBinary(ast:Binary, dest) {
|
2014-11-11 16:54:15 -08:00
|
|
|
var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _operationToFunction(ast.operation), 2, dest);
|
2014-11-10 16:11:29 -08:00
|
|
|
ast.left.visit(this, new Destination(record, 0));
|
|
|
|
ast.right.visit(this, new Destination(record, 1));
|
2014-11-11 17:02:59 -08:00
|
|
|
this.add(record);
|
|
|
|
}
|
2014-11-10 16:11:29 -08:00
|
|
|
|
2014-11-11 17:02:59 -08:00
|
|
|
visitPrefixNot(ast:PrefixNot, dest) {
|
|
|
|
var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _operation_negate, 1, dest);
|
|
|
|
ast.expression.visit(this, new Destination(record, 0));
|
2014-11-10 16:11:29 -08:00
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitAccessMember(ast:AccessMember, dest) {
|
|
|
|
var record = this.construct(PROTO_RECORD_PROPERTY, ast.getter, 0, dest);
|
|
|
|
ast.receiver.visit(this, new Destination(record, null));
|
|
|
|
this.add(record);
|
2014-10-28 12:22:38 -04:00
|
|
|
}
|
|
|
|
|
2014-11-11 16:30:54 -08:00
|
|
|
visitFormatter(ast:Formatter, dest) {
|
2014-11-14 12:34:35 -08:00
|
|
|
var record = this.construct(PROTO_RECORD_FORMATTTER, ast.name, ast.allArgs.length, dest);
|
2014-11-11 16:30:54 -08:00
|
|
|
for (var i = 0; i < ast.allArgs.length; ++i) {
|
|
|
|
ast.allArgs[i].visit(this, new Destination(record, i));
|
|
|
|
}
|
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
2014-11-11 16:54:15 -08:00
|
|
|
visitMethodCall(ast:MethodCall, dest) {
|
|
|
|
var record = this.construct(PROTO_RECORD_METHOD, ast.fn, ast.args.length, dest);
|
|
|
|
ast.receiver.visit(this, new Destination(record, null));
|
|
|
|
for (var i = 0; i < ast.args.length; ++i) {
|
|
|
|
ast.args[i].visit(this, new Destination(record, i));
|
|
|
|
}
|
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitFunctionCall(ast:FunctionCall, dest) {
|
|
|
|
var record = this.construct(PROTO_RECORD_CLOSURE, null, ast.args.length, dest);
|
|
|
|
ast.target.visit(this, new Destination(record, null));
|
|
|
|
for (var i = 0; i < ast.args.length; ++i) {
|
|
|
|
ast.args[i].visit(this, new Destination(record, i));
|
|
|
|
}
|
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
2014-11-11 17:18:09 -08:00
|
|
|
visitConditional(ast:Conditional, dest) {
|
|
|
|
var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _cond, 3, dest);
|
|
|
|
ast.condition.visit(this, new Destination(record, 0));
|
|
|
|
ast.trueExp.visit(this, new Destination(record, 1));
|
|
|
|
ast.falseExp.visit(this, new Destination(record, 2));
|
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
2014-11-11 17:39:40 -08:00
|
|
|
visitKeyedAccess(ast:KeyedAccess, dest) {}
|
|
|
|
|
|
|
|
visitLiteralArray(ast:LiteralArray, dest) {
|
|
|
|
var length = ast.expressions.length;
|
|
|
|
var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _arrayFn(length), length, dest);
|
|
|
|
for (var i = 0; i < length; ++i) {
|
|
|
|
ast.expressions[i].visit(this, new Destination(record, i));
|
|
|
|
}
|
|
|
|
this.add(record);
|
|
|
|
}
|
|
|
|
|
2014-11-11 17:47:45 -08:00
|
|
|
visitLiteralMap(ast:LiteralMap, dest) {
|
|
|
|
var length = ast.values.length;
|
|
|
|
var record = this.construct(PROTO_RECORD_PURE_FUNCTION, _mapFn(ast.keys, length), length, dest);
|
|
|
|
for (var i = 0; i < length; ++i) {
|
|
|
|
ast.values[i].visit(this, new Destination(record, i));
|
|
|
|
}
|
|
|
|
this.add(record);
|
|
|
|
}
|
2014-11-11 17:39:40 -08:00
|
|
|
|
|
|
|
visitChain(ast:Chain, dest){this.unsupported();}
|
|
|
|
|
|
|
|
visitAssignment(ast:Assignment, dest) {this.unsupported();}
|
|
|
|
|
2014-11-18 16:38:36 -08:00
|
|
|
visitTemplateBindings(ast, dest) {this.unsupported();}
|
|
|
|
|
2014-10-28 12:22:38 -04:00
|
|
|
createRecordsFromAST(ast:AST, memento){
|
2014-11-10 16:11:29 -08:00
|
|
|
ast.visit(this, memento);
|
|
|
|
}
|
|
|
|
|
|
|
|
construct(recordType, funcOrValue, arity, dest) {
|
2014-11-19 15:52:01 -08:00
|
|
|
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, dest);
|
2014-10-28 12:22:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
add(protoRecord:ProtoRecord) {
|
|
|
|
if (this.headRecord === null) {
|
|
|
|
this.headRecord = this.tailRecord = protoRecord;
|
|
|
|
} else {
|
|
|
|
this.tailRecord.next = protoRecord;
|
|
|
|
protoRecord.prev = this.tailRecord;
|
|
|
|
this.tailRecord = protoRecord;
|
|
|
|
}
|
|
|
|
}
|
2014-11-11 17:39:40 -08:00
|
|
|
|
|
|
|
unsupported() {
|
|
|
|
throw new BaseException("Unsupported");
|
|
|
|
}
|
2014-11-10 16:11:29 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _operationToFunction(operation:string):Function {
|
|
|
|
switch(operation) {
|
|
|
|
case '+' : return _operation_add;
|
|
|
|
case '-' : return _operation_subtract;
|
|
|
|
case '*' : return _operation_multiply;
|
|
|
|
case '/' : return _operation_divide;
|
|
|
|
case '%' : return _operation_remainder;
|
|
|
|
case '==' : return _operation_equals;
|
|
|
|
case '!=' : return _operation_not_equals;
|
|
|
|
case '<' : return _operation_less_then;
|
|
|
|
case '>' : return _operation_greater_then;
|
|
|
|
case '<=' : return _operation_less_or_equals_then;
|
|
|
|
case '>=' : return _operation_greater_or_equals_then;
|
|
|
|
case '&&' : return _operation_logical_and;
|
|
|
|
case '||' : return _operation_logical_or;
|
|
|
|
default: throw new BaseException(`Unsupported operation ${operation}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _operation_negate(value) {return !value;}
|
|
|
|
function _operation_add(left, right) {return left + right;}
|
|
|
|
function _operation_subtract(left, right) {return left - right;}
|
|
|
|
function _operation_multiply(left, right) {return left * right;}
|
|
|
|
function _operation_divide(left, right) {return left / right;}
|
|
|
|
function _operation_remainder(left, right) {return left % right;}
|
|
|
|
function _operation_equals(left, right) {return left == right;}
|
|
|
|
function _operation_not_equals(left, right) {return left != right;}
|
|
|
|
function _operation_less_then(left, right) {return left < right;}
|
|
|
|
function _operation_greater_then(left, right) {return left > right;}
|
|
|
|
function _operation_less_or_equals_then(left, right) {return left <= right;}
|
|
|
|
function _operation_greater_or_equals_then(left, right) {return left >= right;}
|
|
|
|
function _operation_logical_and(left, right) {return left && right;}
|
|
|
|
function _operation_logical_or(left, right) {return left || right;}
|
2014-11-11 17:39:40 -08:00
|
|
|
function _cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
|
2014-11-11 17:47:45 -08:00
|
|
|
function _arrayFn(length:int) {
|
2014-11-11 17:39:40 -08:00
|
|
|
switch (length) {
|
|
|
|
case 0: return () => [];
|
|
|
|
case 1: return (a1) => [a1];
|
|
|
|
case 2: return (a1, a2) => [a1, a2];
|
|
|
|
case 3: return (a1, a2, a3) => [a1, a2, a3];
|
|
|
|
case 4: return (a1, a2, a3, a4) => [a1, a2, a3, a4];
|
|
|
|
case 5: return (a1, a2, a3, a4, a5) => [a1, a2, a3, a4, a5];
|
|
|
|
case 6: return (a1, a2, a3, a4, a5, a6) => [a1, a2, a3, a4, a5, a6];
|
|
|
|
case 7: return (a1, a2, a3, a4, a5, a6, a7) => [a1, a2, a3, a4, a5, a6, a7];
|
|
|
|
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => [a1, a2, a3, a4, a5, a6, a7, a8];
|
|
|
|
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => [a1, a2, a3, a4, a5, a6, a7, a8, a9];
|
|
|
|
default: throw new BaseException(`Does not support literal arrays with more than 9 elements`);
|
|
|
|
}
|
|
|
|
}
|
2014-11-11 17:47:45 -08:00
|
|
|
function _mapFn(keys:List, length:int) {
|
|
|
|
function buildMap(values) {
|
|
|
|
var res = MapWrapper.create();
|
|
|
|
for(var i = 0; i < keys.length; ++i) {
|
|
|
|
MapWrapper.set(res, keys[i], values[i]);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (length) {
|
|
|
|
case 0: return () => [];
|
|
|
|
case 1: return (a1) => buildMap([a1]);
|
|
|
|
case 2: return (a1, a2) => buildMap([a1, a2]);
|
|
|
|
case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
|
|
|
|
case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
|
|
|
|
case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
|
|
|
|
case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
|
|
|
|
case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
|
|
|
|
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
|
|
|
|
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
|
|
|
|
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
|
|
|
|
}
|
|
|
|
}
|