148 Commits

Author SHA1 Message Date
Anders Eknert afb30d3f9d Add gocritic linter, fix a bunch of stuff (#7377)
Brace yourselves! For there are many touched files here. No changes
in semantics however.

Spent a long time trying out the various optional rules gocritic
provides, and settled for a few of them. There are more I really
like, but that would take many hours to address across the codebase.

Perhaps others find gocritic too pedantic? If so, we can merge the
fixes without enabling the rule.

Signed-off-by: Anders Eknert <anders@styra.com>
2025-02-24 16:28:41 +01:00
Johan Fylling a179a24c48 v1 API
All packages, except for `cmd` and `internal`, have been moved into a new `v1` root package.

Old packages are kept for backwards-compatibility reasons. All contained code is replaced with simple type aliases and proxy functions to `v1` implementations.

Old packages default to the Rego v0 syntax, new `v1` packages default to the Rego v1 syntax.

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2024-12-12 15:27:34 +01:00
Johan Fylling 7bb6dbe36b Preparing for v1 API
Moving (most) source to v1 root package to prepare for v0/v1 API separation.

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2024-12-12 15:09:03 +01:00
Anders Eknert 58ec50b4b0 Fix location for multivalue rules with generated bodies (#7129)
Since there is no body, the location of the head is a better option
than simply using the fist scanned token for location.

Fixes #7128

Signed-off-by: Anders Eknert <anders@styra.com>
2024-10-21 17:57:01 +02:00
Anders Eknert 1d51c1df61 Replace use of yaml.v2 library with yaml.v3 (#7091)
Some quirks encountered in the upgrade:
- Errors reported by the parser are now zero-indexed
- A few errors seemingly reported on the line after the
  error rather than where it happened
- Also a few tests where the line number reported
  *previously* seemed wrong but now is right
- Something different in how the parser unmarshalled to
  the "raw" schema annotation type we used. Changed to
  use a a map instead of that type alias.. but I'm not
  really sure why that had to be done

Perhaps worth looking into this further. But pushing this
now to start that discussion.

Fixes #7090

Signed-off-by: Anders Eknert <anders@styra.com>
2024-10-06 03:54:06 +02:00
Anders Eknert e959bce141 Fix: attach annotation to static part of rule ref (#7052)
Previously, annotations were missing on rules that had variable
components in the ref head. Now we annotate the rule using the
path up to the first var.

Fixes #7050

Signed-off-by: Anders Eknert <anders@styra.com>
2024-09-23 13:33:14 +02:00
Johan Fylling 546eeac58e rego-v1: Future-proofing ast package tests to be 1.0 compatible (#6992)
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2024-09-18 10:22:15 +02:00
Johan Fylling ebe5b01827 Assigning rule key also for if-rules with 2-part non-ground refs (#7004)
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2024-09-12 15:45:20 +02:00
Stephan Renatus 25d21f50d3 ast/parser: add hint to future-proof imports (#6968)
Just an idea, if we ever introduce an

    import awesome

and people start receiving "unknown import" errors on outdated
OPA versions, they'll at least give a hint.

This could be extended to cover the other synthetic imports, like
`rego.*` and `future.*`, but it's a start...

Signed-off-by: Stephan Renatus <stephan@styra.com>
2024-08-28 10:49:34 +02:00
Anders Eknert ecc134bb23 Fix wrong location on metadata parse errors on first line (#6807)
Fixes #6587

Signed-off-by: Anders Eknert <anders@styra.com>
2024-06-11 10:29:00 +02:00
Anders Eknert 8f25aaf56c Include original text in annotation location text attribute (#6808)
Previously it would just say '# METADATA'

Fixes #6779

Signed-off-by: Anders Eknert <anders@styra.com>
2024-06-11 10:05:15 +02:00
Anders Eknert d2e995ea19 Fix blanket "unexpected assign token" error message / usability issue (#6778)
This one has been among my top annoyances, and thanks to @johanfylling,
it was easy to finally track down. Had to update a few tests, but not too
many.

Fixes #6563

Signed-off-by: Anders Eknert <anders@styra.com>
2024-05-31 12:23:55 +02:00
Ashutosh Narkar 4e5c36d05c Include annotations in rule AST (#6771)
Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
Co-authored-by: Johan Fylling <johan.dev@fylling.se>
2024-05-30 14:54:48 +02:00
Johan Fylling 091286b5b4 rego: Setting query Rego-version from configured imports
When `rego.v1` is in the list of imports directly applied on the `rego.Rego` SDK struct, this import, and it's effects, is applied to the query when parsed.
This change affects the `eval` and `bench` commands when the `--imports` flag is used.

Fixes: #6701

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2024-04-22 14:19:22 -07:00
Torin Sandall 3c6e079afd ast: change ident token string (#6435)
This commit changes the string representation of the ident token from
'ident' to 'identifier' as this shows up in error messages and should be
a bit friendlier for users.

Signed-off-by: Torin Sandall <torin@styra.com>
2023-11-25 10:49:31 +01:00
Anders Eknert 69a4f3d769 Add ExcludeLocationFile JSON marshalling option (#6398)
Repeating the name of the file in each location is often redundant,
and large AST trees carry thousands of these attributes. Providing
an option to have them removed at least for serialization (as anything
more would be a breaking change) seems like a good compromise.

For reference, see https://github.com/StyraInc/regal/issues/408

Signed-off-by: Anders Eknert <anders@styra.com>
2023-11-10 15:37:38 +01:00
Johan Fylling 17aafaa55b ast: Making rego.v1 import errors more specific
to whether they concern a rule or function.

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-11-06 09:01:01 -08:00
Johan Fylling 89855df570 rego.v1: Improving support for rules with chained bodies (#6374)
Fixes: #6370
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-11-03 11:29:35 +01:00
Johan Fylling 030923998d rego.v1: Fixing erroneous missing value assignment error (#6368)
for rules/functions that only has value assignment for `else` but not "primary" head, when `rego.v1` is imported.

Fixes: #6364

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-11-01 15:04:50 +01:00
Ashutosh Narkar a56c919a4c ast: skip if keyword requirement for default rule
The `rego.v1` import requires the `if` keyword before
the rule body. We should make an exception for default
rules.

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2023-10-27 10:57:54 -07:00
Ashutosh Narkar 98c300c077 Rename future.compat import
This change renames the `future.compat` import to `rego.v1`.
The latter is clear that it's a declaration that
a module is compatible with a v1 version of OPA.

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2023-10-26 09:13:56 -07:00
Johan Fylling c76d5d6330 ast: future.compat import (#6285)
Adding `future.compat` import for enforcing strict-mode checks and additional `1.0` behavior for the module.

Fixes: #6247
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-10-20 13:32:40 +02:00
Charlie Egan cd0bf5b4d2 Add option to marshal location text (#6234) 2023-09-21 09:40:07 +01:00
Johan Fylling 0431567c26 General refs in rule heads (#5913)
* Adding support for multiple variables at arbitrary locations in rule refs
* Updating type-checker to handle general ref heads

Fixes: #5993
Fixes: #5994

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-08-31 12:29:49 +02:00
Ashutosh Narkar afcf01457c topdown: Honor default keyword on functions
Default functions satisfy the following properties:

* Same arity as other functions with the same name
* Arguments should only be plain variables ie. no composite values. For ex, default f([x]) = 1 is an invalid default function
* Variable names should not be repeated ie. default f(x, x) = 1 is an invalid default function

Fixes: #2445

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2023-07-19 23:54:23 -07:00
Gianluca Oldani 1e38591597 parse: include rule head location when requested
Prior to this commit, when the AST information
were serialized to JSON, rules expressed as

discussed in #5790 included only the location
of their value and not the one of the rule
ref var itself. Now the AST serialized to
JSON includes this information.

Fixes: #5790
Signed-off-by: Gianluca Oldani <oldanigianluca@gmail.com>
2023-04-18 16:55:55 -07:00
Johan Fylling 4bc7491237 ast: Improving error information when metadata yaml fails to compile (#5567)
* ast: Improving error information when metadata yaml fails to compile

* Properly divine Rego location for yaml unmarshal errors
* Error hint for cases where non-legal space is used with yaml key/value separator `:`

Fixes: #4475
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2023-01-19 21:00:21 +01:00
Philip Conrad 5eccbb41a6 ast/parser: Detect function rule head + contains keyword. (#5526)
Previously, we did not detect misuses of the `contains` keyword, which
is only valid as a syntactic sugar for partial set definition rules.

This commit adds some logic to detect when a function rule head has been
paired with the `contains` keyword, which should generally be an error
condition.

Fixes: #5525

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2023-01-05 10:47:32 +01:00
Stephan Renatus 9354f89ff6 ast/parser: fresh wildcards for chained function heads (#5426)
This is a spiritual follow-up to #5412.

A policy like

    f(_) := 1 { true } { true } { true }

would have been pretty-printed as

    f(_0) := 1
    f(_0) := 1
    f(_0) := 1

because of the duplicated wildcards.

They now get the same treatment as "else" gets: fresh wildcards.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-11-29 18:56:34 +01:00
Stephan Renatus 7540f110b8 ast/parser: fix else handling with ref heads (#5425)
There was a case that slipped through the parser, but failed at a later stage.

    a.b[x] { x := input }
    else := 2

It's now caught early.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-11-29 13:35:18 +01:00
Stephan Renatus 280cce64a4 ast/parser: generate new wildcards for else (#5412)
The previous behavior had triggered a check in the formatter for multiple
use of wildcard variables:

     f(_) := true { true }
     else := false

The formatter found `$1`, the `_` argument of f, again in else, and thus
changed it into `_1`:

     f(_1) := true { true }
     else := false

There's no extra meaning to the copied wildcards in `else`; they should not
count as second usage.

Fixes: #5347

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-11-23 12:49:24 -05:00
Anders Eknert 92a210768f Fix assignment rewrite in else formatting (#5400)
Fixes #5348

Signed-off-by: Anders Eknert <anders@eknert.com>
2022-11-17 17:56:49 +01:00
Stephan Renatus 94f8bc7206 ast/parser: fix nightly fuzz case (#5330)
An unchecked ref had made it into RefHead() in one of the parsers
code paths. Now, it's rejected as it should be.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-11-02 10:00:14 +01:00
Stephan Renatus 965301f90e ast: support dotted heads (#4660)
This change allows rules to have string prefixes in their heads -- we've
come to call them "ref heads".

String prefixes means that where before, you had

    package a.b.c
    allow = true

you can now have

    package a
    b.c.allow = true

This allows for more concise policies, and different ways to structure
larger rule corpuses.

Backwards-compatibility:

- There are code paths that accept ast.Module structs that don't necessarily
  come from the parser -- so we're backfilling the rule's Head.Reference
  field from the Name when it's not present.
  This is exposed through (Head).Ref() which always returns a Ref.

  This also affects the `opa parse` "pretty" output:

  With x.rego as

    package x
    import future.keywords
    a.b.c.d if true
    e[x] if true

  we get

    $ opa parse x rego
    module
     package
      ref
       data
       "x"
     import
      ref
       future
       "keywords"

     rule
      head
       ref
        a
        "b"
        "c"
        "d"
       true
      body
       expr index=0
        true
     rule
      head
       ref
        e
        x
       true
      body
       expr index=0
        true

  Note that

    Name: e
    Key: x

  becomes

    Reference: e[x]

  in the output above (since that's how we're parsing it, back-compat edge cases aside)

- One special case for backcompat is `p[x] { ... }`:

    rule                    | ref   | key | value | name
    ------------------------+-------+-----+-------+-----
    p[x] { ... }            | p     | x   | nil   | "p"
    p contains x if { ... } | p     | x   | nil   | "p"
    p[x] if { ... }         | p[x]  | nil | true  | ""

  For interpreting a rule, we now have the following procedure:

  1. if it has a Key, it's a multi-value rule; and its Ref defines the set:

     Head{Key: x, Ref: p} ~> p is a set
     ^-- we'd get this from `p contains x if true`
         or `p[x] { true }` (back compat)

  2. if it has a Value, it's a single-value rule; its Ref may contain vars:

     Head{Ref: p.q.r[s], Value: 12} ~> body determines s, `p.q.r.[s]` is 12
     ^-- we'd get this from `p.q.r[s] = 12 { s := "whatever" }`

     Head{Key: x, Ref: p[x], Value: 3} ~> `p[x]` has value 3, `x` is determined
                                          by the rule body
     ^-- we'd get this from `p[x] = 3 if x := 2`
         or `p[x] = 3 { x := 2 }` (back compat)

     Here, the Key isn't used, it's present for backwards compatibility: for ref-
     less rule heads, `p[x] = 3` used to be a partial object: key x, value 3,
     name "p"

- The destinction between complete rules and partial object rules disappears.
  They're both single-value rules now.

- We're now outputting the refs of the rules completely in error messages, as
  it's hard to make sense of "rule r" when there's rule r in package a.b.c and
  rule b.c.r in package a.

Restrictions/next steps:

- Support for ref head rules in the REPL is pretty poor so far. Anything that
  works does so rather accidentally. You should be able to work with policies
  that contain ref heads, but you cannot interactively define them.
  
  This is because before, we'd looked at REPL input like

      p.foo.bar = true

  and noticed that it cannot be a rule, so it's got to be a query. This is no
  longer the case with ref heads.

- Currently vars in Refs are only allowed in the last position. This is expected
 to change in the future.

- Also, for multi-value rules, we can not have a var at all -- so the following
  isn't supported yet:

      p.q.r[s] contains t if { ... }

-----

Most of the work happens when the RuleTree is derived from the ModuleTree -- in
the RuleTree, it doesn't matter if a rule was `p` in `package a.b.c` or `b.c.p`
in `package a`.

As such, the planner and wasm compiler hasn't seen that many adaptations:

- We're putting rules into the ruletree _including_ the var parts, so

  p.q.a = 1
  p.q.[x] = 2 { x := "b" }

  end up in two different leaves:

  p
  `-> q
       `-> a = 1
       `-> [x] = 2`

- When planing a ref, we're checking if a rule tree node's children have
  var keys, and plan "one level higher" accordingly:

  Both sets of rules, p.q.a and p.q[x] will be planned into one function
  (same as before); and accordingly return an object {"a": 1, "b": 2}

- When we don't have vars in the last ref part, we'll end up planning
  the rules separately. This will have an effect on the IR.

  p.q = 1
  p.r = 2

  Before, these would have been one function; now, it's two. As a result,
  in Wasm, some "object insertion" conflicts can become "var assignment
  conflicts", but that's in line with the now-new view of "multi-value"
  and "single-value" rules, not partial {set/obj} vs complete.
* planner: only check ref.GroundPrefix() for optimizations

In a previous commit, we've only mapped

    p.q.r[7]

as p.q.r;  and as such, also need to lookup the ref

    p.q.r[__local0__]

via p.q.r

(I think. Full disclosure: there might be edge cases here that are unaccounted
for, but right now, I'm aiming for making the existing tests green...)


New compiler stage:

In the compiler, we're having a new early rewriting step to ensure that the
RuleTree's keys are comparible. They're ast.Value, but some of them cause us
grief:

- ast.Object cannot be compared structurally; so

      _, ok := map[ast.Value]bool{ast.NewObject([2]*ast.Term{ast.StringTerm("foo"), ast.StringTerm("bar")}): true}[ast.NewObject([2]*ast.Term{ast.StringTerm("foo"), ast.StringTerm("bar")})]

  `ok` will never be true here.

- ast.Ref is a slice type, not hashable, so adding that to the RuleTree would
  cause a runtime panic:

      p[y.z] { y := input }

  is now rewritten to

    p[__local0__] { y := input; __local0__ := y.z }

This required moving the InitLocalVarGen stage up the chain, but as it's still
below ResolveRefs, we should be OK.

As a consequence, we've had to adapt `oracle` to cope with that rewriting:

1. The compiler rewrites rule head refs early because the rule tree expects
   only simple vars, no refs, in rule head refs. So `p[x.y]` becomes
   `p[local] { local = x.y }`
2. The oracle circles in on the node it's finding the definition for based
   on source location, and the logic for doing that depends on unaltered
   modules.

So here, (2.) is relaxed: the logic for building the lookup node stack can
now cope with generated statements that have been appended to the rule bodies.


There is a peculiarity about ref rules and extents:

See the added tests: having a ref rule implies that we get an empty object
in the full extent:

    package p
    foo.bar if false

makes the extent of data.p: {"foo": {}}

This is somewhat odd, but also follows from the behaviour we have right now
with empty modules:

    package p.foo
    bar if false

this also gives data.p the extent {"foo": {}}.

This could be worked around by recording, in the rule tree, when a node was
added because it's an intermediary with no values, but only children.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-10-14 10:15:54 +02:00
Stephan Renatus 2f01fe904f ast/parser+formatter: allow 'if' in rule 'else'
This follows the same rules as 'if' used with ordinary rules:

1. if the future keyword is present, 'if' will be used in `opa fmt`'s output
2. shorthands are allowed:

    p := true if 2>1
    else := "blah" if 1 < 0
3. the formatter will only use the shorthand if the body was on one line with
   the rest before:

    else := 1 { whatever }

becomes

    else := 1 if whatever

but

    else := 1 {
        whatever
    }

becomes

    else := 1 if {
        whatever
    }

Fixes #5002.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-09-28 12:45:04 -04:00
Stephan Renatus 33ec9fb384 ast: skip rules when parsing a body (or query) (#5104)
* ast.ParserOptions got a new field: SkipRules (bool).

It's used from ParseBody() (and some of its sibling functions with opts) to
instruct the parser to NOT attempt to parse the token stream as ast.Rule.

Using this, we gain more leeway in dealing with tricky ambiguous cases.
Previously, the queries

    set(); {1}

and
    set()
    {1}

haven't been equivalent, because the latter ended up being parsed as a rule.
Now, we can follow the caller intent: if that input string was parsed with
ParseBody(), we'll return the two expressions, same as is the case when parsing
with the (disambiguating) semicolon. If we have not been given ParserOptions
with SkipRules: true -- i.e., the default behaviour -- then it will be just
like before.

Also adds a panic to NewExpr to ensure we never construct anything unexpected
using it.

* ast.ParseBodyWithOpts, will NOT change popts.SkipRules

* rego: skip rules for parsing query

This is underlying the code flow for

    cat query | opa eval --stdin

and now does what you'd expect: treat the input as query, and parse it as such.

Previously, we'd been using the parser opts from the future imports helper, and
would have missed setting SkipRules.

This seems to be the only non-test caller of ParseBodyWithOpts (in OPA).

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-09-14 09:07:10 +02:00
Stephan Renatus 04a3523b22 ast+format: introduce new keywords for rule heads: if and contains
`contains` provides an alternative way to declare partial sets:

    p contains x {
      x := { "foo": "bar"
    }

which is the same as

    p[x] {
      x := { "foo": "bar"
    }

The keyword is enabled by importing `future.keywords.contains`, and
when it _is enabled_, the format will be used for all partial sets in
that file for pretty-printing.

`if` is a new keyword allowing for more readable rule definitions:

The syntax is

    NAME [if] { EXPR [EXPR...] }

and the is a shorthand allows dropping the braces around the expression
if there is only one:

    NAME if EXPR

For example, this allows expressions like

    allow if not deny
    f(xs) if every x in xs { x != "foo" }

The one exception here are partial sets: they cannot use `if` UNLESS
they use `contains`:

    p[x] { x := "foo" }            # valid
    p contains x { x := "bar" }    # valid
    p contains x if { x := "bar" } # valid
    p[x] if { x := "foo" }         # invalid

This is because we want to interpret that differently (as an object
rule defining `p.foo = true`) in the near future.

The formatter works in the same way: if `future.keywords.if` is imported, it
will be used where it can be used.

We don't want to be too eager when it comes to introducing syntactic sugar.

So this will be rewritten, because head and body expression are on the same
line:

    p := 5 if { time.day_of_week() == "Monday" }

    # => p := 5 if time.day_of_week() == "Monday"

but this won't:

    p := 5 if {
       time.day_of_week() == "Monday"
    }

The rationale here is that if the policy author decided that they want this on
an extra line, we won't mess with it.

This also sidesteps the need to check if both the head and the single body
expression have a comment.

This change includes various docs updates. Notable exceptions are the GK docs,
since it will take a while for these keywords to be come available there; and
the frontpage: merging a PR would update the frontpage immediately, and we
don't want to show something there that isn't available in the latest release.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-06-22 10:36:06 +02:00
Philip Conrad dbeb2d6b5a ast/parser: Remove early MHS return in infix parsing. (#4773)
This parser change allows the parser to properly fall through to the RHS
case where it was not doing so before, due to returning early during MHS
parsing in the parseTermIn() function.

Fixes #4672.

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-06-14 20:33:39 +02:00
Johan Fylling aeda97e8ee ast: Extending support for file-level assignments (:=) (#4583)
Updated support for:
* default values
* rules with `else` keyword
* partial rules
* functions

Fixes: #4555

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2022-04-20 13:28:51 +02:00
Johan Fylling c6226621c4 ast: Adding rego.metadata.* built-in functions (#4537)
New functions:
* rego.metadata.chain(): returns the chain of metadata, starting from the active rule, going outward
* rego.metadata.rule(): returns the metadata for the active rule

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2022-04-13 11:46:11 +02:00
Stephan Renatus e9d3828db6 ast/parser: guard against invalid domains for "some" and "every" (#4548)
These would cause a runtime exception when attempting to parse

    some internal.member_2()

This is due to a technicality in how

    some x in xs

is parsed: `x in xs` will first become `internal.member_2(x, xs)`,
and `some internal.member_2(x, xs)` is then further processed. The
assumption that there're always two (resp. three for internal.member_3)
arguments won't hold if a snippet like the one above is fed into
the parser.

Thanks to Norbert Szetei of Doyensec, @doyensec, for reporting this.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-04-05 13:17:11 +02:00
Stephan Renatus 558dbe7951 ast/parser: hint about 'every' future keyword (#4408)
This should inject a hint for when it's likely that a parsed
identifier `every` was meant to be a keyword.

By the usual hinting mechanism in the parser, if the parser proceeds
without any errors, the hint is going to be ignored.

This doesn't look exactly like what we did for `some x in xs` because
the `some` keyword wasn't new (`every` is).

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-03-04 13:52:22 +01:00
Stephan Renatus c4ab4b35c9 ast/parser: parse 'with' on 'some x in xs' expression (#4371)
Fixes #4226.

Also
* adds a YAML test to ensure that this works fine end-to-end.
* ci(pull-request): show input on failure

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-02-22 13:43:02 +01:00
Johan Fylling 987148a02b ast: Adding generic annotations (rich metadata) (#4332)
Relates to: #4331
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2022-02-17 12:19:31 +01:00
Stephan Renatus a36f581987 topdown: evaluate "every"
This includes simple evaluation and partial eval.

For PE, we're deliberately unsophisticated for the first iteration:
if anything is unknown, we'll save the expression.

Due to some technicality, the saved expression's body will be less
clean than usual: copy-propagation is not yet applied.

Besides fixing that, future improvements could unroll the every
expression when the domain is known; or do more work to PE the
expression body when.

Known TODOs: tracing is not yet in the desired state.

There are added test cases in the YAML test case files, and in
topdown_test.go: the tests defined in topdown/topdown_test.go allow
for finer-grained assertions, using `print()` to capture the
evaluation.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-02-11 11:22:19 +01:00
Stephan Renatus a96e1779f3 ast+format: unveil future keywords 'every', forbit negation, copy *Every
Importing `future.keywords.every` will ALSO import `future.keywords.in`,
since the latter is required for the former.

This includes the formatting of the expression itself, and adding
the "future.keywors.every" import if necessary:

This would happen when pretty-printing an AST that was parsed
with ast.ParserOptions enabling the required future keyword:
The import would not be present in the *ast.Module, but it would
be required to parse the pretty-printed result.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-02-11 11:22:19 +01:00
Stephan Renatus 56558df5e1 ast: misc changes re: 'every' (#4327)
* ast/index_test: add cases covering 'every'

Expressions in the body of `every x, y in DOMAIN { BODY }` need to be
ignored: the overall statement could still be true even if BODY contains
undefined references: if the DOMAIN is empty.

The DOMAIN of every, however, will be subject to indexing. This happens
automatically because of previous rewriting steps, but the tests cases
still seemed like a good idea to include.

* ast/parser: give more precise error message for not+[some|every]
* ast/parser: capture future keywords in error messages

Before, the order of the tokens.Token enum had been used to decide if
a token was just a token or a keyword.

Now, we'll look them up, and we account for future keywords.

* ast/parser+compile: account for 'with' on 'every' expr

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-02-07 18:20:43 +01:00
Stephan Renatus a75b74db15 ast: add every future keyword (parser, internal representation) (#4179)
* ast: add 'every' future keyword, parser support, scaffolding

With this commit, "every x in xs { ... }" and "every k, v in { ... }"
will be parsed into a new struct, which is basically

    Every {
      Key, Value *Term
      Domain *Term
      Body Body
    }

This includes the required to changes to visitors, comparisons, copy, ...
all the ceremony required to introduce a new keyword.

* format: format every statements

* ast: hide 'every' from capabilities for now

With this change,

- capabilities.json will NOT mention "every"
- "import future.keywords" will NOT get you "every"
- "import future.keywords.every" will complain about "every" being unknown

In tests, we're passing an unexported field in `ast.ParserOptions`,
which makes the parser treat "every" like it would eventually be
treated when unveiled.

The formatting tests are bluntly SKIPPED for now, to be re-enabled later.

---------------
NB: The work on "every" is ongoing.
Rewriting and evaluation follows. When it's documented, we'll unhide it.
---------------

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-01-18 10:32:20 +01:00
Stephan Renatus e622b7415d ast/parser: don't error when future kw import is redundant (#4237)
When passing `ParserOptions{AllFutureKeywords: true}` or
`ParserOptions{FutureKeywords: []string{"in"}}` to the `ast` package's
parse methods, and when parsing a module that contains

    import future.keywords.in

then we would previously have raised an error: when the parser
encountered the `in`, it complains about not expecting an "in" token
there.

Now, parsing imports is done with a scanner copy that knows nothing
about the future keywords.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-01-17 20:13:00 +01:00
Stephan Renatus c99b645194 ast: fuzzed parser bug, two codeql issues (#3988)
* storage/path.Ref: parse int64 into ast.Number
* ast/PtrRef: guard against giant paths

I don't believe this limit is every going to be reached. But CodeQL had flagged
this, and it's not entirely wrong. Let's error our on giant wonky inputs instead
of seeing what'll happen with it eventually.

* ast/parser: fix bad import alias var

The fuzzer came up with that!

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-11-08 14:10:32 +01:00