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

Added DAX Lexer #2046

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ group :development do
# Ruby 3 no longer ships with a web server
gem 'puma' if RUBY_VERSION >= '3'
gem 'shotgun'
gem 'rackup'
end
2 changes: 1 addition & 1 deletion docs/Docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Pretty sweet. Let's unpack this:
Just replace the `bundle` command with `rake`:

```bash
docker run -t -v $PWD:/app -v /tmp/vendor:/vendor -w /app -e BUNDLE_PATH=/vendor ruby rake
docker run -t -v $PWD:/app -v /tmp/vendor:/vendor -w /app -e BUNDLE_PATH=/vendor ruby bundle exec rake
```

### Running Rack
Expand Down
15 changes: 15 additions & 0 deletions lib/rouge/demos/dax
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
DEFINE MEASURE 'foo'[measure1] =
VAR variable = 4.5
RETURN
SUMX( VALUES( 'bar'[col] ), 'bar'[col] + variable )

EVALUATE
ADDCOLUMNS(
VALUES( 'Date'[Year Month] )
,"@sumBar"
,CALCULATE(
[measure1]
,REMOVEFILTERS()
,USERELATIONSHIP( 'Date'[Date], 'bar'[Order Date] )
)
)
143 changes: 143 additions & 0 deletions lib/rouge/lexers/dax.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class DAX < RegexLexer
title "DAX"
desc "Data Analysis Expressions (DAX) https://learn.microsoft.com/en-us/dax/."
tag 'dax'
filenames '*.dax'
mimetypes

def self.functions
# sources:
# https://learn.microsoft.com/en-us/dax/
# https://learn.microsoft.com/en-us/dax/dax-function-reference
@functions ||= Set.new %w(
ABS ACCRINT ACCRINTM ACOS ACOSH ACOT ACOTH ADDCOLUMNS ADDMISSINGITEMS
ALL ALLCROSSFILTERED ALLEXCEPT ALLNOBLANKROW ALLSELECTED AMORDEGRC
AMORLINC AND APPROXIMATEDISTINCTCOUNT ASIN ASINH ATAN ATANH AVERAGE
AVERAGEA AVERAGEX BETA.DIST BETA.INV BITAND BITLSHIFT BITOR BITRSHIFT
BITXOR BLANK CALCULATE CALCULATETABLE CALENDAR CALENDARAUTO CEILING
CHISQ.DIST CHISQ.DIST.RT CHISQ.INV CHISQ.INV.RT CLOSINGBALANCEMONTH
CLOSINGBALANCEQUARTER CLOSINGBALANCEYEAR COALESCE COLLAPSE COLLAPSEALL
COLUMNSTATISTICS COMBIN COMBINA COMBINEVALUES CONCATENATE CONCATENATEX
CONFIDENCE.NORM CONFIDENCE.T CONTAINS CONTAINSROW CONTAINSSTRING
CONTAINSSTRINGEXACT CONVERT COS COSH COT COTH COUNT COUNTA COUNTAX
COUNTBLANK COUNTROWS COUNTX COUPDAYBS COUPDAYS COUPDAYSNC COUPNCD
COUPNUM COUPPCD CROSSFILTER CROSSJOIN CUMIPMT CUMPRINC CURRENCY
CURRENTGROUP CUSTOMDATA DATATABLE DATE DATEADD DATEDIFF DATESBETWEEN
DATESINPERIOD DATESMTD DATESQTD DATESYTD DATEVALUE DAY DB DDB DEGREES
DETAILROWS DISC DISTINCT DISTINCTCOUNT DISTINCTCOUNTNOBLANK DIVIDE
DOLLARDE DOLLARFR DURATION EARLIER EARLIEST EDATE EFFECT ENDOFMONTH
ENDOFQUARTER ENDOFYEAR EOMONTH ERROR EVALUATEANDLOG EVEN EXACT EXCEPT
EXP EXPAND EXPANDALL EXPON.DIST EXTERNALMEASURE FACT FALSE FILTER
FILTERS FIND FIRST FIRSTDATE FIRSTNONBLANK FIRSTNONBLANKVALUE FIXED
FLOOR FORMAT FV GCD GENERATE GENERATEALL GENERATESERIES GEOMEAN
GEOMEANX GROUPBY HASH HASONEFILTER HASONEVALUE HOUR IF IF.EAGER IFERROR
IGNORE INDEX INFO.ALTERNATEOFDEFINITIONS INFO.ANNOTATIONS
INFO.ATTRIBUTEHIERARCHIES INFO.ATTRIBUTEHIERARCHYSTORAGES
INFO.CALCULATIONGROUPS INFO.CALCULATIONITEMS INFO.CHANGEDPROPERTIES
INFO.COLUMNPARTITIONSTORAGES INFO.COLUMNPERMISSIONS INFO.COLUMNS
INFO.COLUMNSTORAGES INFO.CULTURES INFO.DATACOVERAGEDEFINITIONS
INFO.DATASOURCES INFO.DELTATABLEMETADATASTORAGES
INFO.DETAILROWSDEFINITIONS INFO.DICTIONARYSTORAGES
INFO.EXCLUDEDARTIFACTS INFO.EXPRESSIONS INFO.EXTENDEDPROPERTIES
INFO.FORMATSTRINGDEFINITIONS INFO.FUNCTIONS
INFO.GENERALSEGMENTMAPSEGMENTMETADATASTORAGES INFO.GROUPBYCOLUMNS
INFO.HIERARCHIES INFO.HIERARCHYSTORAGES INFO.KPIS INFO.LEVELS
INFO.LINGUISTICMETADATA INFO.MEASURES INFO.MODEL
INFO.OBJECTTRANSLATIONS INFO.PARQUETFILESTORAGES INFO.PARTITIONS
INFO.PARTITIONSTORAGES INFO.PERSPECTIVECOLUMNS
INFO.PERSPECTIVEHIERARCHIES INFO.PERSPECTIVEMEASURES INFO.PERSPECTIVES
INFO.PERSPECTIVETABLES INFO.QUERYGROUPS INFO.REFRESHPOLICIES
INFO.RELATEDCOLUMNDETAILS INFO.RELATIONSHIPINDEXSTORAGES
INFO.RELATIONSHIPS INFO.RELATIONSHIPSTORAGES INFO.ROLEMEMBERSHIPS
INFO.ROLES INFO.SEGMENTMAPSTORAGES INFO.SEGMENTSTORAGES
INFO.STORAGEFILES INFO.STORAGEFOLDERS INFO.STORAGETABLECOLUMNS
INFO.STORAGETABLECOLUMNSEGMENTS INFO.STORAGETABLES
INFO.TABLEPERMISSIONS INFO.TABLES INFO.TABLESTORAGES
INFO.VARIATIONS INT INTERSECT INTRATE IPMT ISAFTER ISATLEVEL ISBLANK
ISCROSSFILTERED ISEMPTY ISERROR ISEVEN ISFILTERED ISINSCOPE ISLOGICAL
ISNONTEXT ISNUMBER ISO.CEILING ISODD ISONORAFTER ISPMT
ISSELECTEDMEASURE ISSUBTOTAL ISTEXT KEEPFILTERS KEYWORDMATCH LAST
LASTDATE LASTNONBLANK LASTNONBLANKVALUE LCM LEFT LEN LINEST LINESTX LN
LOG LOG10 LOOKUP LOOKUPVALUE LOWER MATCHBY MAX MAXA MAXX MDURATION
MEDIAN MEDIANX MID MIN MINA MINUTE MINX MOD MONTH MOVINGAVERAGE MROUND
NAMEOF NATURALINNERJOIN NATURALLEFTOUTERJOIN NETWORKDAYS NEXT NEXTDAY
NEXTMONTH NEXTQUARTER NEXTYEAR NOMINAL NONVISUAL NORM.DIST NORM.INV
NORM.S.DIST NORM.S.INV NOT NOW NPER ODD ODDFPRICE ODDFYIELD ODDLPRICE
ODDLYIELD OFFSET OPENINGBALANCEMONTH OPENINGBALANCEQUARTER
OPENINGBALANCEYEAR OR ORDERBY PARALLELPERIOD PARTITIONBY PATH
PATHCONTAINS PATHITEM PATHITEMREVERSE PATHLENGTH PDURATION
PERCENTILE.EXC PERCENTILE.INC PERCENTILEX.EXC PERCENTILEX.INC PERMUT PI
PMT POISSON.DIST POWER PPMT PREVIOUS PREVIOUSDAY PREVIOUSMONTH
PREVIOUSQUARTER PREVIOUSYEAR PRICE PRICEDISC PRICEMAT PRODUCT
PRODUCTX PV QUARTER QUOTIENT RADIANS RAND RANDBETWEEN RANGE RANK
RANK.EQ RANKX RATE RECEIVED RELATED RELATEDTABLE REMOVEFILTERS REPLACE
REPT RIGHT ROLLUP ROLLUPADDISSUBTOTAL ROLLUPGROUP ROLLUPISSUBTOTAL
ROUND ROUNDDOWN ROUNDUP ROW ROWNUMBER RRI RUNNINGSUM SAMEPERIODLASTYEAR
SAMPLE SAMPLEAXISWITHLOCALMINMAX SAMPLECARTESIANPOINTSBYCOVER SEARCH
SECOND SELECTCOLUMNS SELECTEDMEASURE SELECTEDMEASUREFORMATSTRING
SELECTEDMEASURENAME SELECTEDVALUE SIGN SIN SINH SLN SQRT SQRTPI
STARTOFMONTH STARTOFQUARTER STARTOFYEAR STDEV.P STDEV.S STDEVX.P
STDEVX.S SUBSTITUTE SUBSTITUTEWITHINDEX SUM SUMMARIZE SUMMARIZECOLUMNS
SUMX SWITCH SYD T.DIST T.DIST.2T T.DIST.RT T.INV T.INV.2T TAN TANH
TBILLEQ TBILLPRICE TBILLYIELD TIME TIMEVALUE TOCSV TODAY TOJSON TOPN
TOPNPERLEVEL TOPNSKIP TOTALMTD TOTALQTD TOTALYTD TREATAS TRIM TRUE
TRUNC UNICHAR UNICODE UNION UPPER USERCULTURE USERELATIONSHIP USERNAME
USEROBJECTID USERPRINCIPALNAME UTCNOW UTCTODAY VALUE VALUES VAR.P VAR.S
VARX.P VARX.S VDB WEEKDAY WEEKNUM WINDOW XIRR XNPV YEAR YEARFRAC YIELD
YIELDDISC YIELDMAT
)
end

def self.statements
# sources:
# https://learn.microsoft.com/en-us/dax/
# https://learn.microsoft.com/en-us/dax/statements-dax
@statements ||= Set.new(%w(
AXIS ROWS COLUMN DEFINE DENSIFY EVALUATE GROUP MEASURE MPARAMETER ORDER BY
RETURN START AT TABLE TOTAL VAR WITH VISUAL SHAPE IN ASC DESC SKIP DENSE
BLANKS FIRST LAST WEEK BOTH NONE ONEWAY ONEWAY_RIGHTFILTERSLEFT ONEWAY_LEFTFILTERSRIGHT
KEEP REL
))
end

def self.data_types
# sources:
# https://dax.guide/datatypes/
@data_types ||= Set.new(%w(
BINARY BOOLEAN CURRRENCY DATETIME DECIMAL INTEGER STRING VARIANT
))
end

state :root do
rule (/\s+/m), Text::Whitespace
rule (/\/{2}.*/), Comment::Single
rule (/\/\*([\s\S]*).*\*\//m), Comment::Multiline
rule (/\b-?\d+(e\d+)?(\.\d+)?/), Literal::Number
rule (/'(?:[^']|'')*'(?!')(?:\[[ \w]+\])?|\w+\[[ \w]+\]/), Name::Entity #Tables & Column
rule (/\[[ \w]+\]/), Name::Entity #Measure
rule (/"(?:[^"]|"")*"(?!")/), Literal::String

rule %r/\w+/ do |m|
if self.class.functions.include? m[0].upcase
token Keyword
elsif self.class.statements.include? m[0].upcase
token Keyword
elsif self.class.data_types.include? m[0].upcase
token Keyword::Type
else
token Name
end
end

rule (/(?:[:\-+*\/=^><]|&{1,2}|\|{2}|\b(IN|NOT))/i), Operator
rule (/[;:()\[\]\{\},.]/), Punctuation
end

end
end
end
16 changes: 16 additions & 0 deletions spec/lexers/dax_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::DAX do
let(:subject) { Rouge::Lexers::DAX.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.dax'
end

end

end
81 changes: 81 additions & 0 deletions spec/visual/samples/dax
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Measure
Measure1 =
VAR currentState = SELECTEDVALUE( States [State])
VAR currentDate = SELECTEDVALUE( 'Date'[Date] ) + 1
VAR dataInScope =
CALCULATETABLE(
DISTINCT( data )
,data[DateTime] < currentDate
,REMOVEFILTERS( 'Date'[Date] )
,REMOVEFILTERS( 'States'[State] )
)
RETURN
COUNTROWS(
CALCULATETABLE(
FILTER(
INDEX( 1, dataInScope, ORDERBY( data[DateTime], DESC), , PARTITIONBY( data[TestID] ))
,[State] = currentState
)
,REMOVEFILTERS( 'Calendar'[Date] )
)
)

// Query
EVALUATE
var OuterDateFilter = FILTER( 'Date', 'Date'[Fiscal Month] in {"FY20-May", "FY20-Jul", "FY21-Jul"} || 'Date'[Fiscal Year] in {"FY18", "FY19"} || 'Date'[Fiscal Week] in {"FY17-W01", "FY17-W02", "FY17-W03"} )
var Dates =
ADDCOLUMNS(
SUMMARIZE(
OuterDateFilter
,'Date'[Fiscal Year]
,'Date'[Fiscal Quarter]
,'Date'[Fiscal Month]
,'Date'[Fiscal Week]
,'Date'[Date]
)
,"@Islands" // Calcualtes the difference between the date and the row number of the table, if there is a date gap then we start a new island
,DATEDIFF(
RANKX ( OuterDateFilter, 'Date'[Date], , 1 )
,'Date'[Date]
, DAY
)
)
var Islands =
SUMMARIZE(
Dates
, [@Islands]
, "IslandString"
, var MinDate = MIN ( 'Date'[Date] )
var MaxDate = MAX ( 'Date'[Date] )
var MinFiscalYear = CALCULATE( MIN( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Year] ))
var MaxFiscalYear = CALCULATE( MAX( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Year] ))
var MinFiscalQuarter = CALCULATE( MIN( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Quarter] ))
var MaxFiscalQuarter = CALCULATE( MAX( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Quarter] ))
var MinFiscalMonth = CALCULATE( MIN( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Month] ))
var MaxFiscalMonth = CALCULATE( MAX( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Month] ))
var MinFiscalWeek = CALCULATE( MIN( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Week] ))
var MaxFiscalWeek = CALCULATE( MAX( 'Date'[Date] ), ALLEXCEPT( 'Date', 'Date'[Fiscal Week] ))
var GranularityCheck =
SWITCH(
true
, MinDate = MinFiscalYear && MaxDate = MaxFiscalYear
, CALCULATE( MAX( 'Date'[Fiscal Year] ), 'Date'[Date] = MinDate ) & "--" & CALCULATE( MAX( 'Date'[Fiscal Year] ), 'Date'[Date] = MaxDate )
, MinDate = MinFiscalQuarter && MaxDate = MaxFiscalQuarter
, CALCULATE( MAX( 'Date'[Fiscal Quarter] ), 'Date'[Date] = MinDate ) & "--" & CALCULATE( MAX( 'Date'[Fiscal Quarter] ), 'Date'[Date] = MaxDate )
, MinDate = MinFiscalMonth && MaxDate = MaxFiscalMonth
, CALCULATE( MAX( 'Date'[Fiscal Month] ), 'Date'[Date] = MinDate ) & "--" & CALCULATE( MAX( 'Date'[Fiscal Month] ), 'Date'[Date] = MaxDate )
, MinDate = MinFiscalWeek && MaxDate = MaxFiscalWeek
, CALCULATE( MAX( 'Date'[Fiscal Week] ), 'Date'[Date] = MinDate ) & "--" & CALCULATE( MAX( 'Date'[Fiscal Week] ), 'Date'[Date] = MaxDate )
,MinDate & "--" & MaxDate
)
return
GranularityCheck
)
var ReturnString =
CONCATENATEX (
Islands
, [IslandString]
, UNICHAR ( 10 )
)
return
{ReturnString}