mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 21:31:11 +01:00
Add built-in functions.
This commit is contained in:
parent
709f564f21
commit
32db51f005
22
lib/src/ast/sass/callable_declaration.dart
Normal file
22
lib/src/ast/sass/callable_declaration.dart
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
20
lib/src/callable/built_in.dart
Normal file
20
lib/src/callable/built_in.dart
Normal 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;
|
||||
}
|
18
lib/src/callable/user_defined.dart
Normal file
18
lib/src/callable/user_defined.dart
Normal 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);
|
||||
}
|
@ -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
14
lib/src/functions.dart
Normal 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())));
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user