Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SassScript Maps #481

Merged
merged 10 commits into from
Oct 2, 2014
Merged

Implement SassScript Maps #481

merged 10 commits into from
Oct 2, 2014

Conversation

xzyfer
Copy link
Contributor

@xzyfer xzyfer commented Sep 9, 2014

This PR implements SassScript Maps and the API as of 3.3.14. There were changes to some map APIs in 3.4 none of which are implemented here.

I think this is about ready to start the feedback process. I'm not a c/c++ developer by trade so there's likely plenty of room for improvement.

TODO

  • map parsing
  • length
  • map-get
  • map-merge
  • map-remove (3.3 syntax)
  • map-keys
  • map-values
  • map-has-key
  • keywords

Known issues

Possible issues

  • have I sourcemaped correctly?

list and map variable keyword arguments in same definition

I think this can be implemented as a separate feature. It's probably best suited to being shipped with call.

Spec

Specs for SassScript Maps have been added in sass/sass-spec#44. Theses specs admittedly focus primarily on the map-* functions and not the parsing of Maps.

/cc @akhleung

if (!(m->concrete_type() == Expression::MAP || m->concrete_type() == Expression::LIST)) {
error("$map: is not a map for `" + string(sig) + "`", path, position);
}
Map* map = dynamic_cast<Map*>(env["$map"]);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error checking block is repeated a few times. Is there a better way?

      Expression* m = ARG("$map", Expression);
      Expression* v = ARG("$key", Expression);
      if (!(m->concrete_type() == Expression::MAP || m->concrete_type() == Expression::LIST)) {
        error("$map: is not a map for `" + string(sig) + "`", path, position);
      }
      Map* map = dynamic_cast<Map*>(env["$map"]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ... I assume that this function is supposed to accept lists and treat them as maps that have integer keys? If you need to handle both cases, I can't think of a good way to do the type checking that wouldn't involve some boilerplate ... I suppose you could write variants of ARG and get_arg that can take 2 or 3 different types, and check that the argument matches one of them....

Also, even though you check for both Maps and Lists, you seem to cast only to a Map afterward -- is this an omission?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I'll clarify. The pattern with these function is they take a map, or an empty map () which is indistinguishable from a list, and is actually treated a list by the parser.

So in essence I'm just trying to determine an $map is infact a map with zero or more keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caveat with this approach is that I'm not sure what'll happen with a list in fact passed in and coerced with Map* map = dynamic_cast<Map*>(env["$map"]);. Sass would return an error

$map: ("foo" "bar" "baz") is not a map for `map-get'

http://sassmeister.com/gist/ecfdc8072ab078cbdbc3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, now that I've consulted the Ruby Sass docs and code snippets, I'm not sure why you're checking if the $map argument might also be a list -- can't you just require it to be a map? Then you could just use

Map* m = ARG("$map", Map);

on the first line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I appreciate the feedback. Generally speaking I think I'm doing mostly the right the things.

I'm just not sure whether it's the correct way to do them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the issue I ran into with

Map* m = ARG("$map", Map);

is that this

foo: map-get((), foo);

would resulting in

error: argument $mapofmap-get($map, $key) must be a map

because ARG thinks () is a list. I suppose the correct thing to do would be to update ARG to understand that () is both an empty list and map.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth seeing if you can add a template specialization of get_arg (specialized for Map) that does some extra checking to see if the arg is an empty list, and if it is, then replace the arg with a newly-constructed empty map and return a pointer to that map. (If you try this, you'll need to modify get_arg to take a Context object so that the memory allocations can be tracked.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I agree that's the sane thing to do. I'll give it a go today.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 57dada0

@HamptonMakes
Copy link
Member

EEEEE!!!!

@xzyfer
Copy link
Contributor Author

xzyfer commented Sep 16, 2014

It'd be ideal @akhleung could get his feedback in before Sunday US time.
I have company allotted hack time to work on this coming up this Monday AU time (Sunday US time).

@akhleung
Copy link

Hey sorry for the delay -- I've been ill for the past few days. I'll review this and comment on it today!

for (size_t i = 0, L = l->length(); i < L; ++i) {
if (!eq((*l)[i]->key(), (*r)[i]->key(), ctx)) return false;
if (!eq((*l)[i]->value(), (*r)[i]->value(), ctx)) return false;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This equality check is naive, and could potentially be a performance issue with the right inputs. Maybe this is ok for the first iteration?

An alternative approach would be to maintain a has for each map or key value pair for equality comparisons.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems fine for a first pass; we can optimize later if it becomes a problem.

@am11
Copy link
Contributor

am11 commented Sep 20, 2014

@akhleung, it's good to have you back! 👍
Hope you are doing well. :)

@xzyfer
Copy link
Contributor Author

xzyfer commented Sep 22, 2014

I've updated the Map casting boilerplate with a get_arg specialised for maps as discussed. It's much nicer now 💯

I think this is pretty much good to go.

@HamptonMakes
Copy link
Member

@akhleung can we have a special "Maps in LibSass" announcement for SassConf this week?

@akhleung
Copy link

Sure, I'll get this merged in time for SassConf.

akhleung pushed a commit that referenced this pull request Oct 2, 2014
Implement SassScript Maps
@akhleung akhleung merged commit 612ec27 into sass:master Oct 2, 2014
@OliverJAsh OliverJAsh mentioned this pull request Oct 2, 2014
@lunelson
Copy link

lunelson commented Oct 6, 2014

I'm getting syntax failure on the following.

$nest: (
  alpha: (
    beta: (gamma: delta),
    epsilon: 2 3 4,
    zeta: 'hi'
  )
);

.test {
  out: map-get(map-get(map-get($nest, alpha), beta), gamma);
  out: map-get(map-get($nest, alpha), epsilon);
  out: map-get(map-get($nest, alpha), zeta);
}

output with ruby sass 3.4.4 is the following:

.test {
  out: delta;
  out: 2 3 4;
  out: "hi";
}

Running sassc on the command line with this however, I get unclosed parenthesis error for line 4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants