Add built-in functions.

This commit is contained in:
Natalie Weizenbaum 2016-08-27 02:06:15 -07:00 committed by Natalie Weizenbaum
parent 709f564f21
commit 32db51f005
10 changed files with 146 additions and 62 deletions

View File

@ -0,0 +1,22 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import 'argument_declaration.dart';
import 'statement.dart';
abstract class CallableDeclaration implements Statement {
final String name;
final ArgumentDeclaration arguments;
final List<Statement> children;
final FileSpan span;
CallableDeclaration(this.name, this.arguments, Iterable<Statement> children,
{this.span})
: children = new List.unmodifiable(children);
}

View File

@ -8,18 +8,10 @@ import '../../visitor/interface/statement.dart';
import 'argument_declaration.dart';
import 'statement.dart';
class FunctionDeclaration implements Statement {
final String name;
final ArgumentDeclaration arguments;
final List<Statement> children;
final FileSpan span;
FunctionDeclaration(this.name, this.arguments, Iterable<Statement> children,
{this.span})
: children = new List.unmodifiable(children);
class FunctionDeclaration extends CallableDeclaration {
FunctionDeclaration(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, {FileSpan span})
: super(name, arguments, children, span: span);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitFunctionDeclaration(this);

View File

@ -8,18 +8,10 @@ import '../../visitor/interface/statement.dart';
import 'argument_declaration.dart';
import 'statement.dart';
class MixinDeclaration implements Statement {
final String name;
final ArgumentDeclaration arguments;
final List<Statement> children;
final FileSpan span;
MixinDeclaration(this.name, this.arguments, Iterable<Statement> children,
{this.span})
: children = new List.unmodifiable(children);
class MixinDeclaration extends CallableDeclaration {
MixinDeclaration(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, {FileSpan span})
: super(name, arguments, children, span: span);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitMixinDeclaration(this);

View File

@ -9,6 +9,7 @@ export 'at_rule.dart';
export 'argument_declaration.dart';
export 'argument_invocation.dart';
export 'argument.dart';
export 'callable_declaration.dart';
export 'comment.dart';
export 'declaration.dart';
export 'extend_rule.dart';

View File

@ -2,23 +2,13 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:source_span/source_span.dart';
import 'ast/sass/statement.dart';
import 'environment.dart';
class Callable {
final String name;
export 'callable/built_in.dart';
export 'callable/user_defined.dart';
final ArgumentDeclaration arguments;
abstract class Callable {
String get name;
final List<Statement> children;
final Environment environment;
final FileSpan span;
Callable(this.name, this.arguments, Iterable<Statement> children,
this.environment, {this.span})
: children = new List.unmodifiable(children);
ArgumentDeclaration get arguments;
}

View File

@ -0,0 +1,20 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import '../ast/sass/statement.dart';
import '../callable.dart';
import '../value.dart';
typedef Value _Callback(List<Value> arguments);
class BuiltInCallable implements Callable {
final _Callback callback;
final String name;
final ArgumentDeclaration arguments;
BuiltInCallable(this.name, this.arguments,
Value callback(List<Value> arguments))
: callback = callback;
}

View File

@ -0,0 +1,18 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import '../ast/sass/statement.dart';
import '../callable.dart';
import '../environment.dart';
class UserDefinedCallable implements Callable {
final CallableDeclaration declaration;
final Environment environment;
String get name => declaration.name;
ArgumentDeclaration get arguments => declaration.arguments;
UserDefinedCallable(this.declaration, this.environment);
}

View File

@ -3,6 +3,7 @@
// https://opensource.org/licenses/MIT.
import 'callable.dart';
import 'functions.dart';
import 'value.dart';
import 'utils.dart';
@ -27,7 +28,9 @@ class Environment {
_functions = [normalizedMap()],
_functionIndices = normalizedMap(),
_mixins = [normalizedMap()],
_mixinIndices = normalizedMap();
_mixinIndices = normalizedMap() {
defineCoreFunctions(this);
}
Environment._(this._variables, this._variableIndices, this._functions,
this._functionIndices, this._mixins, this._mixinIndices);

14
lib/src/functions.dart Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'ast/sass/statement.dart';
import 'callable.dart';
import 'environment.dart';
import 'value.dart';
void defineCoreFunctions(Environment environment) {
environment.setFunction(new BuiltInCallable("inspect",
new ArgumentDeclaration([new Argument("value")]),
(arguments) => new SassIdentifier(arguments.single.toString())));
}

View File

@ -40,9 +40,8 @@ class PerformVisitor extends StatementVisitor
final _extender = new Extender();
PerformVisitor() : this._(new Environment());
PerformVisitor._(this._environment);
PerformVisitor([Environment environment])
: _environment = environment ?? new Environment();
void visit(node) {
if (node is Statement) {
@ -126,29 +125,27 @@ class PerformVisitor extends StatementVisitor
}
void visitFunctionDeclaration(FunctionDeclaration node) {
_environment.setFunction(new Callable(
node.name, node.arguments, node.children, _environment.closure(),
span: node.span));
_environment.setFunction(
new UserDefinedCallable(node, _environment.closure()));
}
void visitInclude(Include node) {
var mixin = _environment.getMixin(node.name);
var mixin = _environment.getMixin(node.name) as UserDefinedCallable;
if (mixin == null) throw node.span.message("Undefined mixin.");
if (node.children != null) {
throw node.span.message("Mixin doesn't accept a content block.");
}
_runCallable(node.arguments, mixin, node.span, () {
for (var statement in mixin.children) {
_runUserDefinedCallable(node.arguments, mixin, node.span, () {
for (var statement in mixin.declaration.children) {
statement.accept(this);
}
});
}
void visitMixinDeclaration(MixinDeclaration node) {
_environment.setMixin(new Callable(
node.name, node.arguments, node.children, _environment.closure(),
span: node.span));
_environment.setMixin(
new UserDefinedCallable(node, _environment.closure()));
}
void visitMediaRule(MediaRule node) {
@ -284,14 +281,22 @@ class PerformVisitor extends StatementVisitor
if (plainName != null) {
var function = _environment.getFunction(plainName);
if (function != null) {
return _runCallable(node.arguments, function, node.span, () {
for (var statement in function.children) {
var returnValue = statement.accept(this);
if (returnValue is Value) return returnValue;
}
if (function is BuiltInCallable) {
return _runBuiltInCallable(node.arguments, function, node.span);
} else if (function is UserDefinedCallable) {
return _runUserDefinedCallable(node.arguments, function, node.span,
() {
for (var statement in function.declaration.children) {
var returnValue = statement.accept(this);
if (returnValue is Value) return returnValue;
}
throw function.span.message("Function finished without @return.");
});
throw function.declaration.span.message(
"Function finished without @return.");
});
} else {
return null;
}
}
}
@ -309,8 +314,8 @@ class PerformVisitor extends StatementVisitor
return new SassIdentifier("$name(${arguments.join(', ')})");
}
/*=T*/ _runCallable/*<T>*/(ArgumentInvocation arguments, Callable callable,
FileSpan span, /*=T*/ run()) {
/*=T*/ _runUserDefinedCallable/*<T>*/(ArgumentInvocation arguments,
UserDefinedCallable callable, FileSpan span, /*=T*/ run()) {
return _withEnvironment(callable.environment, () => _environment.scope(() {
var pair = _evaluateArguments(arguments, span);
var positional = pair.first;
@ -330,7 +335,8 @@ class PerformVisitor extends StatementVisitor
for (var i = positional.length; i < declaredArguments.length; i++) {
var argument = declaredArguments[i];
_environment.setVariable(argument.name,
named.remove(argument.name) ?? argument.defaultValue?.accept(this));
named.remove(argument.name) ??
argument.defaultValue?.accept(this));
}
// TODO: use a full ArgList object
@ -349,6 +355,32 @@ class PerformVisitor extends StatementVisitor
}));
}
Value _runBuiltInCallable(ArgumentInvocation arguments,
BuiltInCallable callable, FileSpan span) {
var pair = _evaluateArguments(arguments, span);
var positional = pair.first;
var named = pair.last;
_verifyArguments(positional, named, callable.arguments, span);
var declaredArguments = callable.arguments.arguments;
for (var i = positional.length; i < declaredArguments.length; i++) {
var argument = declaredArguments[i];
positional.add(named.remove(argument.name) ??
argument.defaultValue?.accept(this));
}
// TODO: use a full ArgList object
if (callable.arguments.restArgument != null) {
var rest = positional.length > declaredArguments.length
? positional.sublist(declaredArguments.length)
: const <Value>[];
positional.add(new SassList(rest, ListSeparator.comma));
}
return callable.callback(positional);
}
Pair<List<Value>, Map<String, Value>> _evaluateArguments(
ArgumentInvocation arguments, FileSpan span) {
var positional = arguments.positional