///|
fn Scope::flow_scope(self : Scope) -> Scope {
{ ..Scope::new(Some(self)), flow: true, }
}
///|
fn Scope::assign(
self : Scope,
name : String,
value : SassValue,
) -> Unit raise ParseError {
if name.contains(".") {
let (owner, key) = self.qualified_owner(name)
owner.vars[key] = value
return
}
if self.flow && !self.vars.contains(name) {
if self.parent is Some(parent) && parent.get(name) is Some(_) {
parent.assign(name, value)
return
}
}
if self.parent is None && !self.vars.contains(name) {
if self.star_variable(name) is Some((owner, key)) {
owner.vars[key] = value
return
}
}
self.vars[name] = value
}
///|
fn control_variable(source : String) -> String raise ParseError {
let value = source.trim().to_owned()
if !value.has_prefix("$") ||
value.length() < 2 ||
!value[1:].to_owned().to_array().iter().all(name_char) {
raise Invalid("invalid loop variable")
}
identifier(value[1:].to_owned())
}
///|
fn render_loop(
header : String,
body : Array[Statement],
parents : Array[String],
scope : Scope,
emitter : Emitter,
depth : Int,
content : Content?,
raw : Bool,
prefix : String,
) -> Bool raise ParseError {
if header.has_prefix("@while ") {
let expression = parse_expression(header[7:].to_owned())
let frame = scope.flow_scope()
while frame.eval_ast(expression, true, 0).truth() {
frame.charge()
render(body, parents, frame, emitter, depth + 1, content, raw, prefix)
if frame.returned is Some(cell) && cell.val is Some(_) {
break
}
}
true
} else if header.has_prefix("@for ") {
let parts = header[5:].split(" from ").to_array()
if parts.length() != 2 {
raise Invalid("for requires from")
}
let name = control_variable(parts[0].to_owned())
let mut bounds = parts[1].split(" through ").to_array()
let inclusive = bounds.length() == 2
if !inclusive {
bounds = parts[1].split(" to ").to_array()
}
if bounds.length() != 2 {
raise Invalid("for requires through or to")
}
let first = require_int(scope.evaluate(bounds[0].to_owned()))
let last = require_int(scope.evaluate(bounds[1].to_owned()))
let direction = if first <= last { 1 } else { -1 }
let frame = scope.flow_scope()
let mut i = first
while (if direction > 0 {
i < last || (inclusive && i == last)
} else {
i > last || (inclusive && i == last)
}) {
frame.charge()
frame.vars[name] = numeric(i.to_double())
render(body, parents, frame, emitter, depth + 1, content, raw, prefix)
if i == last || (frame.returned is Some(cell) && cell.val is Some(_)) {
break
}
i += direction
}
true
} else if header.has_prefix("@each ") {
let pieces = header[6:].split(" in ").to_array()
if pieces.length() < 2 {
raise Invalid("each requires in")
}
let names = split_top(pieces[0].to_owned(), ',').map(control_variable)
let expression = pieces[1:].to_owned().join(" in ")
let values = scope.evaluate(expression).items()
let frame = scope.flow_scope()
for value in values {
frame.charge()
let components = if names.length() == 1 { [value] } else { value.items() }
for i in 0.. Unit raise ParseError {
for node in nodes {
match node {
Leaf(value) =>
if !(value.has_prefix("$") ||
value.has_prefix("@return ") ||
value.has_prefix("@error ") ||
value.has_prefix("@warn ") ||
value.has_prefix("@debug ")) {
raise Invalid("function may only contain value statements")
}
Block(header, body) => {
if !(header.has_prefix("@if ") ||
header == "@else" ||
header.has_prefix("@else if ") ||
header.has_prefix("@each ") ||
header.has_prefix("@for ") ||
header.has_prefix("@while ")) {
raise Invalid("function cannot emit CSS")
}
validate_function(body)
}
}
}
}
///|
fn Scope::use_standard(self : Scope, source : String) -> Unit raise ParseError {
if self.parent is Some(_) {
raise Invalid("use must be at root")
}
let parts = source.split(" as ").to_array()
if parts.length() > 2 {
raise Invalid("invalid module prefix_name")
}
let path = require_text(self.evaluate(parts[0].to_owned())).0
if !path.has_prefix("sass:") {
raise Invalid("file module resolver required")
}
let name = path[5:].to_owned()
if !["math", "list", "map", "string", "meta"].contains(name) {
raise Invalid("unsupported standard module")
}
let prefix_name = if parts.length() == 2 {
parts[1].trim().to_owned()
} else {
name
}
if prefix_name == "*" ||
prefix_name.is_empty() ||
!prefix_name.to_array().iter().all(name_char) {
raise Invalid("invalid standard module prefix_name")
}
if self.standard_modules.contains(prefix_name) ||
self.module_scopes.contains(prefix_name) {
raise Invalid("module namespace already used")
}
self.standard_modules[prefix_name] = name
}