Merge branch 'develop' into declare-test
* develop: Added type to appease bisect Minor additions to datetime units Trying to appease the CI tests, which are failing here for some reason. Delete Reducer_Peggy_GeneratedParser.js Formatted Code Moved DateTime functionality into separate file remove unit evaluation test generic unit support numbers with units (tested) Cleaned up arithmetic operations of time durations Simple better format for Duration toString More sophisticated makeFromYear function, that accepts floats Added better Duration toString and Date makeWithYear functionality Simple dateTime integration
This commit is contained in:
commit
9185719641
|
@ -189,6 +189,19 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
||||||
{expression.value.map((r) => `"${r}"`).join(", ")}
|
{expression.value.map((r) => `"${r}"`).join(", ")}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
|
case "date":
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Date" showTypes={showTypes}>
|
||||||
|
{expression.value.toDateString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case "timeDuration": {
|
||||||
|
return (
|
||||||
|
<VariableBox heading="Time Duration" showTypes={showTypes}>
|
||||||
|
<NumberShower precision={3} number={expression.value} />
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
case "lambda":
|
case "lambda":
|
||||||
return (
|
return (
|
||||||
<FunctionChart
|
<FunctionChart
|
||||||
|
|
|
@ -251,6 +251,10 @@ describe("Peggy parse", () => {
|
||||||
"{(::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}",
|
"{(::map (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
describe("unit", () => {
|
||||||
|
testParse("1m", "{(::fromUnit_m 1)}")
|
||||||
|
testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("parsing new line", () => {
|
describe("parsing new line", () => {
|
||||||
|
|
|
@ -181,5 +181,9 @@ function createTsExport(
|
||||||
return tag("string", x.value);
|
return tag("string", x.value);
|
||||||
case "EvSymbol":
|
case "EvSymbol":
|
||||||
return tag("symbol", x.value);
|
return tag("symbol", x.value);
|
||||||
|
case "EvDate":
|
||||||
|
return tag("date", x.value);
|
||||||
|
case "EvTimeDuration":
|
||||||
|
return tag("timeDuration", x.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,14 @@ export type rescriptExport =
|
||||||
| {
|
| {
|
||||||
TAG: 9; // EvSymbol
|
TAG: 9; // EvSymbol
|
||||||
_0: string;
|
_0: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 10; // EvDate
|
||||||
|
_0: Date;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 11; // EvTimeDuration
|
||||||
|
_0: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type rescriptDist =
|
type rescriptDist =
|
||||||
|
@ -86,6 +94,8 @@ export type squiggleExpression =
|
||||||
| tagged<"boolean", boolean>
|
| tagged<"boolean", boolean>
|
||||||
| tagged<"distribution", Distribution>
|
| tagged<"distribution", Distribution>
|
||||||
| tagged<"number", number>
|
| tagged<"number", number>
|
||||||
|
| tagged<"date", Date>
|
||||||
|
| tagged<"timeDuration", number>
|
||||||
| tagged<"record", { [key: string]: squiggleExpression }>;
|
| tagged<"record", { [key: string]: squiggleExpression }>;
|
||||||
|
|
||||||
export { lambdaValue };
|
export { lambdaValue };
|
||||||
|
@ -127,6 +137,10 @@ export function convertRawToTypescript(
|
||||||
return tag("string", result._0);
|
return tag("string", result._0);
|
||||||
case 9: // EvSymbol
|
case 9: // EvSymbol
|
||||||
return tag("symbol", result._0);
|
return tag("symbol", result._0);
|
||||||
|
case 10: // EvDate
|
||||||
|
return tag("date", result._0);
|
||||||
|
case 11: // EvTimeDuration
|
||||||
|
return tag("number", result._0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,19 @@ type errorValue =
|
||||||
| REArrayIndexNotFound(string, int)
|
| REArrayIndexNotFound(string, int)
|
||||||
| REAssignmentExpected
|
| REAssignmentExpected
|
||||||
| REDistributionError(DistributionTypes.error)
|
| REDistributionError(DistributionTypes.error)
|
||||||
| REOperationError(Operation.operationError)
|
| REExpectedType(string)
|
||||||
| REExpressionExpected
|
| REExpressionExpected
|
||||||
| REFunctionExpected(string)
|
| REFunctionExpected(string)
|
||||||
| REFunctionNotFound(string)
|
| REFunctionNotFound(string)
|
||||||
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
|
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
|
||||||
| REMacroNotFound(string)
|
| REMacroNotFound(string)
|
||||||
| RENotAFunction(string)
|
| RENotAFunction(string)
|
||||||
|
| REOperationError(Operation.operationError)
|
||||||
| RERecordPropertyNotFound(string, string)
|
| RERecordPropertyNotFound(string, string)
|
||||||
| RESymbolNotFound(string)
|
| RESymbolNotFound(string)
|
||||||
| RESyntaxError(string)
|
| RESyntaxError(string)
|
||||||
| RETodo(string) // To do
|
| RETodo(string) // To do
|
||||||
| REExpectedType(string)
|
| REUnitNotFound(string)
|
||||||
|
|
||||||
type t = errorValue
|
type t = errorValue
|
||||||
|
|
||||||
|
@ -52,4 +53,5 @@ let errorToString = err =>
|
||||||
| RESyntaxError(desc) => `Syntax Error: ${desc}`
|
| RESyntaxError(desc) => `Syntax Error: ${desc}`
|
||||||
| RETodo(msg) => `TODO: ${msg}`
|
| RETodo(msg) => `TODO: ${msg}`
|
||||||
| REExpectedType(typeName) => `Expected type: ${typeName}`
|
| REExpectedType(typeName) => `Expected type: ${typeName}`
|
||||||
|
| REUnitNotFound(unitName) => `Unit not found: ${unitName}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,8 +248,7 @@ basicValue = valueConstructor / basicLiteral
|
||||||
|
|
||||||
basicLiteral
|
basicLiteral
|
||||||
= string
|
= string
|
||||||
/ float
|
/ number
|
||||||
/ integer
|
|
||||||
/ boolean
|
/ boolean
|
||||||
/ dollarIdentifier
|
/ dollarIdentifier
|
||||||
|
|
||||||
|
@ -263,6 +262,15 @@ string 'string'
|
||||||
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
|
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
|
||||||
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
|
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
|
||||||
|
|
||||||
|
number = number:(float / integer) unit:identifier?
|
||||||
|
{
|
||||||
|
if (unit === null)
|
||||||
|
{ return number }
|
||||||
|
else
|
||||||
|
{ return makeFunctionCall('fromUnit_'+unit.value, [number])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
integer 'integer'
|
integer 'integer'
|
||||||
= d+ !"\." ![e]i
|
= d+ !"\." ![e]i
|
||||||
{ return nodeInteger(parseInt(text()))}
|
{ return nodeInteger(parseInt(text()))}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
module EV = ReducerInterface_ExpressionValue
|
||||||
|
type expressionValue = EV.expressionValue
|
||||||
|
|
||||||
|
let dateDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
||||||
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
|
> => {
|
||||||
|
switch call {
|
||||||
|
| ("toString", [EvDate(t)]) => EV.EvString(DateTime.Date.toString(t))->Ok->Some
|
||||||
|
| ("makeDateFromYear", [EvNumber(year)]) =>
|
||||||
|
switch DateTime.Date.makeFromYear(year) {
|
||||||
|
| Ok(t) => EV.EvDate(t)->Ok->Some
|
||||||
|
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error->Some
|
||||||
|
}
|
||||||
|
| ("dateFromNumber", [EvNumber(f)]) => EV.EvDate(DateTime.Date.fromFloat(f))->Ok->Some
|
||||||
|
| ("toNumber", [EvDate(f)]) => EV.EvNumber(DateTime.Date.toFloat(f))->Ok->Some
|
||||||
|
| ("subtract", [EvDate(d1), EvDate(d2)]) =>
|
||||||
|
switch DateTime.Date.subtract(d1, d2) {
|
||||||
|
| Ok(d) => EV.EvTimeDuration(d)->Ok
|
||||||
|
| Error(e) => Error(RETodo(e))
|
||||||
|
}->Some
|
||||||
|
| ("subtract", [EvDate(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvDate(DateTime.Date.subtractDuration(d1, d2))->Ok->Some
|
||||||
|
| ("add", [EvDate(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvDate(DateTime.Date.addDuration(d1, d2))->Ok->Some
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let durationDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
||||||
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
|
> => {
|
||||||
|
switch call {
|
||||||
|
| ("toString", [EvTimeDuration(t)]) => EV.EvString(DateTime.Duration.toString(t))->Ok->Some
|
||||||
|
| ("minutes", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromMinutes(f))->Ok->Some
|
||||||
|
| ("fromUnit_minutes", [EvNumber(f)]) =>
|
||||||
|
EV.EvTimeDuration(DateTime.Duration.fromMinutes(f))->Ok->Some
|
||||||
|
| ("hours", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromHours(f))->Ok->Some
|
||||||
|
| ("fromUnit_hours", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromHours(f))->Ok->Some
|
||||||
|
| ("days", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromDays(f))->Ok->Some
|
||||||
|
| ("fromUnit_days", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromDays(f))->Ok->Some
|
||||||
|
| ("years", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromYears(f))->Ok->Some
|
||||||
|
| ("fromUnit_years", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromYears(f))->Ok->Some
|
||||||
|
| ("toHours", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toHours(f))->Ok->Some
|
||||||
|
| ("toMinutes", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toMinutes(f))->Ok->Some
|
||||||
|
| ("toDays", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toDays(f))->Ok->Some
|
||||||
|
| ("toYears", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toYears(f))->Ok->Some
|
||||||
|
| ("add", [EvTimeDuration(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvTimeDuration(DateTime.Duration.add(d1, d2))->Ok->Some
|
||||||
|
| ("subtract", [EvTimeDuration(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvTimeDuration(DateTime.Duration.subtract(d1, d2))->Ok->Some
|
||||||
|
| ("multiply", [EvTimeDuration(d1), EvNumber(d2)]) =>
|
||||||
|
EV.EvTimeDuration(DateTime.Duration.multiply(d1, d2))->Ok->Some
|
||||||
|
| ("divide", [EvTimeDuration(d1), EvNumber(d2)]) =>
|
||||||
|
EV.EvTimeDuration(DateTime.Duration.divide(d1, d2))->Ok->Some
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dispatch = (call: EV.functionCall, env: DistributionOperation.env): option<
|
||||||
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
|
> => {
|
||||||
|
switch dateDispatch(call, env) {
|
||||||
|
| Some(r) => Some(r)
|
||||||
|
| None =>
|
||||||
|
switch durationDispatch(call, env) {
|
||||||
|
| Some(r) => Some(r)
|
||||||
|
| None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ type rec expressionValue =
|
||||||
| EvRecord(record)
|
| EvRecord(record)
|
||||||
| EvString(string)
|
| EvString(string)
|
||||||
| EvSymbol(string)
|
| EvSymbol(string)
|
||||||
|
| EvDate(Js.Date.t)
|
||||||
|
| EvTimeDuration(float)
|
||||||
and record = Js.Dict.t<expressionValue>
|
and record = Js.Dict.t<expressionValue>
|
||||||
and externalBindings = record
|
and externalBindings = record
|
||||||
and lambdaValue = {
|
and lambdaValue = {
|
||||||
|
@ -51,6 +53,8 @@ let rec toString = aValue =>
|
||||||
| EvSymbol(aString) => `:${aString}`
|
| EvSymbol(aString) => `:${aString}`
|
||||||
| EvRecord(aRecord) => aRecord->toStringRecord
|
| EvRecord(aRecord) => aRecord->toStringRecord
|
||||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||||
|
| EvDate(date) => DateTime.Date.toString(date)
|
||||||
|
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||||
}
|
}
|
||||||
and toStringRecord = aRecord => {
|
and toStringRecord = aRecord => {
|
||||||
let pairs =
|
let pairs =
|
||||||
|
@ -73,6 +77,8 @@ let toStringWithType = aValue =>
|
||||||
| EvRecord(_) => `Record::${toString(aValue)}`
|
| EvRecord(_) => `Record::${toString(aValue)}`
|
||||||
| EvString(_) => `String::${toString(aValue)}`
|
| EvString(_) => `String::${toString(aValue)}`
|
||||||
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||||
|
| EvDate(_) => `Date::${toString(aValue)}`
|
||||||
|
| EvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let argsToString = (args: array<expressionValue>): string => {
|
let argsToString = (args: array<expressionValue>): string => {
|
||||||
|
@ -116,6 +122,8 @@ type expressionValueType =
|
||||||
| EvtRecord
|
| EvtRecord
|
||||||
| EvtString
|
| EvtString
|
||||||
| EvtSymbol
|
| EvtSymbol
|
||||||
|
| EvtDate
|
||||||
|
| EvtTimeDuration
|
||||||
|
|
||||||
type functionCallSignature = CallSignature(string, array<expressionValueType>)
|
type functionCallSignature = CallSignature(string, array<expressionValueType>)
|
||||||
type functionDefinitionSignature =
|
type functionDefinitionSignature =
|
||||||
|
@ -133,6 +141,8 @@ let valueToValueType = value =>
|
||||||
| EvRecord(_) => EvtRecord
|
| EvRecord(_) => EvtRecord
|
||||||
| EvString(_) => EvtArray
|
| EvString(_) => EvtArray
|
||||||
| EvSymbol(_) => EvtSymbol
|
| EvSymbol(_) => EvtSymbol
|
||||||
|
| EvDate(_) => EvtDate
|
||||||
|
| EvTimeDuration(_) => EvtTimeDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
||||||
|
@ -152,6 +162,8 @@ let valueTypeToString = (valueType: expressionValueType): string =>
|
||||||
| EvtRecord => `Record`
|
| EvtRecord => `Record`
|
||||||
| EvtString => `String`
|
| EvtString => `String`
|
||||||
| EvtSymbol => `Symbol`
|
| EvtSymbol => `Symbol`
|
||||||
|
| EvtDate => `Date`
|
||||||
|
| EvtTimeDuration => `Duration`
|
||||||
}
|
}
|
||||||
|
|
||||||
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
||||||
|
|
|
@ -18,9 +18,11 @@ let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<
|
||||||
expressionValue,
|
expressionValue,
|
||||||
'e,
|
'e,
|
||||||
> =>
|
> =>
|
||||||
ReducerInterface_GenericDistribution.dispatch(call, environment) |> E.O.default(
|
switch ReducerInterface_GenericDistribution.dispatch(call, environment) {
|
||||||
chain(call, environment),
|
| Some(r) => r
|
||||||
)
|
| None =>
|
||||||
|
ReducerInterface_DateTime.dispatch(call, environment) |> E.O.default(chain(call, environment))
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||||
|
|
||||||
|
|
79
packages/squiggle-lang/src/rescript/Utility/DateTime.res
Normal file
79
packages/squiggle-lang/src/rescript/Utility/DateTime.res
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
module Duration = {
|
||||||
|
//Stores in Unix milliseconds
|
||||||
|
type t = float
|
||||||
|
let minute = Belt.Float.fromInt(60 * 1000)
|
||||||
|
let hour = Belt.Float.fromInt(60 * 60 * 1000)
|
||||||
|
let day = Belt.Float.fromInt(24 * 60 * 60 * 1000)
|
||||||
|
let year = Belt.Float.fromInt(24 * 60 * 60 * 1000) *. 365.25
|
||||||
|
let fromFloat = (f: float): t => f
|
||||||
|
let toFloat = (d: t): float => d
|
||||||
|
let fromMinutes = (h: float): t => h *. minute
|
||||||
|
let fromHours = (h: float): t => h *. hour
|
||||||
|
let fromDays = (d: float): t => d *. day
|
||||||
|
let fromYears = (y: float): t => y *. year
|
||||||
|
let toMinutes = (t: t): float => t /. minute
|
||||||
|
let toHours = (t: t): float => t /. hour
|
||||||
|
let toDays = (t: t): float => t /. day
|
||||||
|
let toYears = (t: t): float => t /. year
|
||||||
|
|
||||||
|
let toString = (t: t): string => {
|
||||||
|
let shouldPluralize = f => f != 1.0
|
||||||
|
let display = (f: float, s: string) =>
|
||||||
|
`${E.Float.with3DigitsPrecision(f)} ${s}${shouldPluralize(f) ? "s" : ""}`
|
||||||
|
let abs = Js.Math.abs_float(t)
|
||||||
|
if abs >= year {
|
||||||
|
display(t /. year, "year")
|
||||||
|
} else if abs >= day {
|
||||||
|
display(t /. day, "day")
|
||||||
|
} else if abs >= hour {
|
||||||
|
display(t /. hour, "hour")
|
||||||
|
} else if abs >= minute {
|
||||||
|
display(t /. minute, "minute")
|
||||||
|
} else {
|
||||||
|
E.Float.toFixed(t) ++ "ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let add = (t1: t, t2: t): t => t1 +. t2
|
||||||
|
let subtract = (t1: t, t2: t): t => t1 -. t2
|
||||||
|
let multiply = (t1: t, t2: float): t => t1 *. t2
|
||||||
|
let divide = (t1: t, t2: float): t => t1 /. t2
|
||||||
|
}
|
||||||
|
|
||||||
|
module Date = {
|
||||||
|
//The Rescript/JS implementation of Date is pretty mediocre. It would be good to improve upon later.
|
||||||
|
type t = Js.Date.t
|
||||||
|
let toFloat = Js.Date.getTime
|
||||||
|
let getFullYear = Js.Date.getFullYear
|
||||||
|
let toString = Js.Date.toDateString
|
||||||
|
let fromFloat = Js.Date.fromFloat
|
||||||
|
let fmap = (t: t, fn: float => float) => t->toFloat->fn->fromFloat
|
||||||
|
let subtract = (t1: t, t2: t) => {
|
||||||
|
let (f1, f2) = (toFloat(t1), toFloat(t2))
|
||||||
|
let diff = f1 -. f2
|
||||||
|
if diff < 0.0 {
|
||||||
|
Error("Cannot subtract a date by one that is in its future")
|
||||||
|
} else {
|
||||||
|
Ok(Duration.fromFloat(diff))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let addDuration = (t: t, duration: Duration.t) => fmap(t, t => t +. duration)
|
||||||
|
let subtractDuration = (t: t, duration: Duration.t) => fmap(t, t => t -. duration)
|
||||||
|
//The Js.Date.makeWithYM function accepts a float, but only treats it as a whole number.
|
||||||
|
//Our version takes an integer to make this distinction clearer.
|
||||||
|
let makeWithYearInt = (y: int): result<t, string> => {
|
||||||
|
if y < 100 {
|
||||||
|
Error("Year must be over 100")
|
||||||
|
} else if y > 200000 {
|
||||||
|
Error("Year must be less than 200000")
|
||||||
|
} else {
|
||||||
|
Ok(Js.Date.makeWithYM(~year=Belt.Float.fromInt(y), ~month=0.0, ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let makeFromYear = (year: float): result<t, string> => {
|
||||||
|
let floor = year->Js.Math.floor_float
|
||||||
|
makeWithYearInt(Belt.Float.toInt(floor))->E.R2.fmap(earlyDate => {
|
||||||
|
let diff = year -. floor
|
||||||
|
earlyDate->addDuration(diff *. Duration.year)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user