#include #include #include #include #include #include #include #include static char Name[] = "Ken Reneris. Units ala Unix style"; #define DBL double #define MAXTYPE 5 extern UCHAR *UnitTab[]; UCHAR token[] = " \t\n"; typedef struct _UNIT { struct _UNIT *Next; PSZ UnitName; PSZ Conversion; } UNIT, *PUNIT; PUNIT UnitList; BOOLEAN Muund; typedef struct _HALFVALUE { DBL Accum; ULONG NoType; struct { PUNIT Unit; ULONG Pow; } Type [MAXTYPE]; } HALFVALUE, *PHALFVALUE; typedef struct _FULLVALUE { ULONG Fuzz; HALFVALUE Nom; HALFVALUE Dom; } FULLVALUE, *PFULLVALUE; struct { PSZ Prefix; ULONG stringlen; DBL Scaler; } BuiltInScalers[] = { "giga", 4, 1000000000.0, "mega", 4, 1000000.0, "kilo", 4, 1000.0, "centi", 5, 0.01, "milli", 5, 0.001, "micro", 5, 0.000001, "nano", 4, 0.000000001, "decinano", 8, 0.0000000001, NULL }; struct { PSZ BaseType; PSZ DefaultDump; } BuiltInDumps[] = { "sec", "hour second millisec microsec nanosec", "bit", "terabyte gigabyte megabyte kilobyte dword byte bit", NULL }; #define ASSERT(_exp_) if (_exp_) { AssertFailed (__FILE__, __LINE__); } PVOID zalloc (IN ULONG len); PSZ StrDup (IN PSZ String); VOID ReadUnitTab (VOID); PUNIT LookupUnit (PSZ UnitName); VOID DumpValue (IN PFULLVALUE Value); VOID InitializeValue (IN PFULLVALUE Value); VOID ConvertValue (PFULLVALUE, PSZ, PFULLVALUE, PSZ); BOOLEAN ProcessString (PUNIT, PSZ, PFULLVALUE); PSZ CopyUnitName (PSZ Out, PSZ String); PSZ CopyWord (PSZ Out, PSZ String); PSZ SkipSpace (PSZ String); BOOLEAN MatchPattern (PUCHAR String, PUCHAR Pattern); VOID ReduceTypes (IN OUT PHALFVALUE MValue, IN OUT PHALFVALUE DValue); VOID AddTypes (IN OUT PHALFVALUE Dest, IN PHALFVALUE Child); BOOLEAN DumpMatchingTypes (PSZ Str); VOID GetInput (PSZ Desc, PSZ Str); VOID AssertFailed (PSZ FileName, ULONG LineNo); void _CRTAPI1 main(int argc, char **argv) { UCHAR have[80], want[80], want2[200]; FULLVALUE hValue, wValue; PSZ p; ULONG i; ReadUnitTab (); if (argc > 1) { // // Arguments on the command line. Argv[1] is "have" // argv++; argc -= 2; strcpy (have, *(argv++)); if (DumpMatchingTypes (have)) { exit (0); } if (!ProcessString (NULL, have, &hValue)) { printf ("Usage: Units [have] [want]\n"); exit (1); } // // If no Argv[2], then check for default dump // if (!argc && hValue.Nom.NoType) { for (i=0; BuiltInDumps[i].BaseType; i++) { if (strcmp (BuiltInDumps[i].BaseType, hValue.Nom.Type[0].Unit->UnitName) == 0) { // // Dump defaults // p = BuiltInDumps[i].DefaultDump; while (*p) { p = CopyWord(want, p); if (ProcessString (NULL, want, &wValue)) { ConvertValue (&hValue, have, &wValue, want); } } break; } } } // // Dump argv[2..n] // for ( ; argc; argc--, argv++) { if (!ProcessString (NULL, *argv, &wValue)) { exit (1); } ConvertValue (&hValue, have, &wValue, *argv); } exit (1); } // // Interactive... ask "have" & "want" // for (; ;) { for (; ;) { GetInput ("You have: ", have); if (ProcessString (NULL, have, &hValue)) { break; } } GetInput ("You want: ", want2); p = want2; do { p = CopyWord (want, p); if (ProcessString (NULL, want, &wValue)) { ConvertValue (&hValue, have, &wValue, want); } } while (*p); printf ("\n"); } return ; } VOID GetInput ( PSZ Desc, PSZ Str ) { for (; ;) { printf (Desc); gets (Str); _strlwr (Str); if (strcmp (Str, "q") == 0) { exit (1); } if (!DumpMatchingTypes (Str)) { break; } } } BOOLEAN DumpMatchingTypes (PSZ Str) { PSZ Title, Line; ULONG LineNo; UCHAR UnitName[80]; BOOLEAN DumpTitle; if (!strchr (Str, '*') && !strchr (Str, '?') && !strchr(Str, '[')) { return FALSE; } // // Dump matching known unitnames // printf ("\nKnown types/groups matching: %s\n", Str); Title = NULL; for (LineNo = 0; UnitTab[LineNo]; LineNo++) { Line = UnitTab[LineNo]; if (Line[0] == '/') { Title = Line; DumpTitle = MatchPattern (Title, Str); } CopyUnitName (UnitName, Line); if (!UnitName[0]) { continue; } if (MatchPattern (UnitName, Str) || DumpTitle) { if (Title) { printf ("%s\n", Title); Title = NULL; } printf (" %s\n", Line); } } printf ("\n"); return TRUE; } PSZ SkipSpace (PSZ String) { while (*String && (*String == ' ' || *String < ' ' || *String == '^')) { String ++; } return String; } PSZ CopyNumber (PSZ Out, PSZ String) { while (*String >= '0' && *String <= '9' || *String == '.') { *(Out++) = *(String++); } *Out = 0; return String; } PSZ CopyWord (PSZ Out, PSZ String) { UCHAR c; while (*String) { if (*String <= ' ') { break; } *(Out++) = *(String++); } *Out = 0; return SkipSpace (String); } PSZ CopyUnitName (PSZ Out, PSZ String) { UCHAR c; while (c = *String) { if (c >= '0' && c <= '9' || c == '.') { break; } if (c == '-' || c == '+' || c == '/' || c == ' ') { break; } if (c == '^' || c < ' ') { String++; continue; } *(Out++) = *(String++); } *Out = 0; return String; } VOID AssertFailed (PSZ FileName, ULONG LineNo) { printf ("Assert failed - file %s line %d\n", FileName, LineNo); exit (1); } PSZ GetBaseType ( IN PSZ Out, IN PHALFVALUE HValue ) { ULONG i; if (HValue->NoType == 0) { Out += sprintf (Out, "constant"); } else { for (i=0; i < HValue->NoType; i++) { Out += sprintf (Out, "%s%s", i ? "-" : "", HValue->Type[i].Unit->UnitName); if (HValue->Type[i].Pow != 1) { Out += sprintf (Out, "^%d", HValue->Type[i].Pow); } } } return Out; } VOID GetBaseTypes ( OUT PSZ Out, IN PFULLVALUE Value ) /** * Returns ascii dump of values data type */ { PUNIT Unit; ULONG i; Out = GetBaseType (Out, &Value->Nom); if (Value->Dom.NoType) { Out += sprintf (Out, "/"); Out = GetBaseType (Out, &Value->Dom); } } VOID DumpValue ( IN PFULLVALUE Value ) { UCHAR s[80]; GetBaseTypes (s, Value); printf ("%g/%g type %s\n", Value->Nom.Accum, Value->Dom.Accum, s); } VOID SortType ( IN PHALFVALUE Value ) { ULONG i, j; ULONG hpow; PUNIT hunit; // // Sort by lowest power, then alphabetical // for (i=0; i < Value->NoType; i++) { for (j=i+1; j < Value->NoType; j++) { if (Value->Type[i].Pow > Value->Type[j].Pow || (Value->Type[i].Pow == Value->Type[j].Pow && strcmp (Value->Type[i].Unit->UnitName, Value->Type[j].Unit->UnitName) > 0)) { // swap hpow = Value->Type[i].Pow; hunit = Value->Type[i].Unit; Value->Type[i].Pow = Value->Type[j].Pow; Value->Type[i].Unit = Value->Type[j].Unit; Value->Type[j].Pow = hpow; Value->Type[j].Unit = hunit; } } } } VOID FormatDbl ( OUT PSZ s, IN DBL Value ) /** * Function to print double "Value" into string "s". This * functions sole purpose is to get a better readable reresentation * of the value into ascii */ { PSZ p1, p2, dp; UCHAR t[80]; LONG i; i = 18 - sprintf (t, "%.1f", Value); if (i < 0) { i = 0; } sprintf (t, "%.*f", i, Value); // // strip off trailing zeros // for (dp=t; *dp; dp++) { if (*dp == '.') { for (p1=p2=dp+1; *p2; p2++) { if (*p2 != '0') { p1 = p2; } } p1[1] = 0; if (p1 == dp+1 && p1[0] == '0') { // it's ".0" remove the whole thing *dp = 0; } break; } } i = dp - t; // # of digits before decimal point i = i % 3; if (i == 0) { i = 3; } // // Copy to decimal point while adding commas // for (p1=s, p2=t; *p2 && *p2 != '.'; p2++) { if (i-- == 0) { *(p1++) = ','; i = 2; } *(p1++) = *p2; } // // Copy remainer // do { *(p1++) = *p2; } while (*(p2++)); // // Did result == 0? Probabily lost precision // if (strcmp (s, "0") == 0) { sprintf (s, "%.18g", Value); } } VOID ConvertValue ( IN PFULLVALUE hValue, IN PSZ have, IN PFULLVALUE wValue, IN PSZ want ) { DBL ans; UCHAR s1[80], s2[80], cf[80]; FULLVALUE Junk1, Junk2; BOOLEAN flag; DBL hAccum, wAccum; PSZ p1, p2, p3, p4, p5; have = SkipSpace(have); want = SkipSpace(want); hAccum = hValue->Nom.Accum / hValue->Dom.Accum; wAccum = wValue->Nom.Accum / wValue->Dom.Accum; ans = hAccum / wAccum; p3 = ""; p5 = NULL; // // See if types match by checking if they cancle each other out // Junk1 = *hValue; Junk2 = *wValue; AddTypes (&Junk1.Nom, &Junk2.Dom); AddTypes (&Junk1.Dom, &Junk2.Nom); ReduceTypes (&Junk1.Nom, &Junk1.Dom); if (Junk1.Nom.NoType + Junk1.Dom.NoType != 0) { // // See if types are inverse // Junk1 = *hValue; Junk2 = *wValue; AddTypes (&Junk1.Nom, &Junk2.Nom); AddTypes (&Junk1.Dom, &Junk2.Dom); ReduceTypes (&Junk1.Nom, &Junk1.Dom); if (Junk1.Nom.NoType + Junk1.Dom.NoType == 0) { // inverse result ans = 1.0 / (hAccum / (1.0 / wAccum)); p5 = "Warning"; } else { // types are not conforming p5 = "Conformance"; } } cf[0] = 0; if (p5) { SortType (&hValue->Nom); SortType (&hValue->Dom); SortType (&wValue->Nom); SortType (&wValue->Dom); GetBaseTypes (s1, hValue); GetBaseTypes (s2, wValue); sprintf (cf, " (%s: %s -> %s)", p5, s1, s2); } FormatDbl (s1, ans); // fancy sprintf (s2, "%.g", ans); // bland p1 = (have[0] >= 'a' && have[0] <= 'z') ? "1" : ""; p2 = (hValue->Fuzz | wValue->Fuzz) ? "(fuzzy) " : "", printf (" %s%s -> %s %s%s%s%s\n", p1, have, s1, p2, p3, want, cf); p4 = strchr (s2, 'e'); if (p4 && !strchr(s1,'e') && atoi(p4+2) > 9) { // print bland answer as well printf (" %s%s -> %s %s%s%s%s\n", p1, have, s2, p2, p3, want, cf); } } BOOLEAN ProcessString ( IN PUNIT Unit, IN PSZ String, OUT PFULLVALUE ReturnValue ) { UCHAR s[80], c; ULONG i, j; FULLVALUE ChildValue; PHALFVALUE MValue, DValue, hldvalue; ReturnValue->Fuzz = 0; MValue = &ReturnValue->Nom; DValue = &ReturnValue->Dom; MValue->Accum = 1.0; MValue->NoType = 0; DValue->Accum = 1.0; DValue->NoType = 0; String = SkipSpace(String); c = *String; if (c == '*') { // // This is a base value // MValue->NoType = 1; MValue->Type[0].Unit = Unit; MValue->Type[0].Pow = 1; return TRUE; } if (c >= '0' && c <= '9' || c == '.') { // // Constant multiplcation // String = CopyNumber (s, String); String = SkipSpace(String); MValue->Accum *= atof(s); } if (*String == '|') { // // Constant Division // String++; String = CopyNumber (s, String); if (s[0]) { DValue->Accum *= atof(s); } } if (*String == '+' || *String == '-') { // // 10^x // s[0] = *(String++); String = CopyNumber (s+1, String); MValue->Accum *= pow (10.0, atof(s)); } for (; ;) { String = SkipSpace(String); if (!*String) { break; } switch (*String) { case '/': // flip denominator & numerator hldvalue = MValue; MValue = DValue; DValue = hldvalue; String++; continue; // get next token case '-': // bugbug?? - just skip these???? String++; continue; default: break; } // // Find sub unit type // String = CopyUnitName (s, String); Unit = LookupUnit (s); if (!Unit) { // // Check for common scaler prefix on keyword // for (i=0; BuiltInScalers[i].Prefix; i++) { if (strncmp (s, BuiltInScalers[i].Prefix, BuiltInScalers[i].stringlen) == 0) { // Scale the value & skip word prefix MValue->Accum *= BuiltInScalers[i].Scaler; Unit = LookupUnit (s + BuiltInScalers[i].stringlen); break; } } if (!Unit) { printf ("Unit type '%s' unkown\n", s); return FALSE; } } // // Get conversion value for this component // if (!ProcessString (Unit, Unit->Conversion, &ChildValue)) { return FALSE; } if (strcmp (Unit->UnitName, "fuzz") == 0) { ReturnValue->Fuzz = 1; } if (*String >= '1' && *String <= '9') { // raise power i = *(String++) - '0'; ChildValue.Nom.Accum = pow (ChildValue.Nom.Accum, i); ChildValue.Dom.Accum = pow (ChildValue.Dom.Accum, i); for (j=0; j < ChildValue.Nom.NoType; j++) { ChildValue.Nom.Type[j].Pow *= i; } for (j=0; j < ChildValue.Dom.NoType; j++) { ChildValue.Dom.Type[i].Pow *= i; } } // // Merge values from child // ReturnValue->Fuzz |= ChildValue.Fuzz; MValue->Accum *= ChildValue.Nom.Accum; DValue->Accum *= ChildValue.Dom.Accum; // // Merge data types from child // AddTypes (MValue, &ChildValue.Nom); AddTypes (DValue, &ChildValue.Dom); ReduceTypes (MValue, DValue); } return TRUE; } VOID AddTypes ( IN OUT PHALFVALUE Dest, IN PHALFVALUE Child ) /** * Add's types from Child to Dest. If the data type already exist in * dest then it's power is raised; otherwise the new type is added to the list * */ { ULONG i, j; for (i=0; i < Child->NoType; i++) { for (j=0; j < Dest->NoType; j++) { if (Child->Type[i].Unit == Dest->Type[j].Unit) { // unit already is destionation - move it Dest->Type[j].Pow += Child->Type[i].Pow; Child->Type[i].Unit = NULL; Child->Type[i].Pow = 0; } } if (Child->Type[i].Unit) { // unit not in destionation - add it j = (Dest->NoType++); ASSERT (j >= MAXTYPE); Dest->Type[j].Unit = Child->Type[i].Unit; Dest->Type[j].Pow = Child->Type[i].Pow; } } } VOID ReduceTypes ( IN OUT PHALFVALUE MValue, IN OUT PHALFVALUE DValue ) /** * Divides & cancles data types. */ { ULONG i, j, k; BOOLEAN Restart; Restart = TRUE; while (Restart) { Restart = FALSE; for (i=0; i < MValue->NoType; i++) { for (j=0; j < DValue->NoType; j++) { if (MValue->Type[i].Unit == DValue->Type[j].Unit) { // matching types - reduce MValue->Type[i].Pow -= DValue->Type[j].Pow; DValue->Type[j].Unit = NULL; DValue->Type[j].Pow = 0; } if (DValue->Type[j].Pow == 0) { // pull this type out of the denominator for (k=j+1; k < DValue->NoType; k++) { DValue->Type[k-1] = DValue->Type[k]; } DValue->NoType -= 1; Restart = TRUE; continue; } } if (MValue->Type[i].Pow == 0) { // pull this type out of the numerator for (k=i+1; k < DValue->NoType; k++) { DValue->Type[k-1] = DValue->Type[k]; } MValue->NoType -= 1; Restart = TRUE; continue; } } } } VOID ReadUnitTab (VOID) { UCHAR Line[80], s[80]; ULONG LineNo; PUNIT Unit; PSZ p, p1; for (LineNo = 0; UnitTab[LineNo]; LineNo++) { strcpy (Line, UnitTab[LineNo]); // // Strip off trailing blanks // for (p=p1=Line; *p; p++) { if (*p != ' ') { p1 = p; } } p1[1] = 0; // // First word is type of unit // p = SkipSpace (Line); if (*p == 0 || *p == '/') { continue; } p = CopyUnitName (s, p); Unit = zalloc (sizeof(UNIT)); Unit->UnitName = StrDup (s); // // Rest of line is Conversion string // p = SkipSpace (p); Unit->Conversion = StrDup (p); // // Add Unit to list of all known units // Unit->Next = UnitList; UnitList = Unit; } } PUNIT LookupUnit (PSZ UnitName) { PUNIT Unit; UCHAR Name[40]; ULONG i; for (i=0; UnitName[i]; i++) { Name[i] = UnitName[i]; if (Name[i] >= '0' && Name[i] <= '9') { break; } } Name[i] = 0; for (Unit=UnitList; Unit; Unit = Unit->Next) { if (strcmp (Unit->UnitName, Name) == 0) { break; } } return Unit; } PSZ StrDup (IN PSZ String) { ULONG len; PSZ p; // allocate & duplicate string len = strlen(String)+1; p = malloc (len); if (!p) { printf ("Out of memory\n"); exit (1); } memcpy (p, String, len); return p; } PVOID zalloc (IN ULONG len) { PVOID p; // allocate & zero memory p = malloc (len); if (!p) { printf ("Out of memory\n"); exit (1); } memset (p, 0, len); return p; } /*** MatchPattern - check if string matches pattern * * Supports: * * - Matches any number of characters (including zero) * ? - Matches any 1 character * [set] - Matches any charater to charater in set * (set can be a list or range) * */ BOOLEAN MatchPattern (PUCHAR String, PUCHAR Pattern) { UCHAR c, p, l; for (; ;) { switch (p = *Pattern++) { case 0: // end of pattern return *String ? FALSE : TRUE; // if end of string TRUE case '*': while (*String) { // match zero or more char if (MatchPattern (String++, Pattern)) return TRUE; } return MatchPattern (String, Pattern); case '?': if (*String++ == 0) // match any one char return FALSE; // not end of string break; case '[': if ( (c = *String++) == 0) // match char set return FALSE; // syntax c = tolower(c); l = 0; while (p = *Pattern++) { if (p == ']') // if end of char set, then return FALSE; // no match found if (p == '-') { // check a range of chars? p = *Pattern; // get high limit of range if (p == 0 || p == ']') return FALSE; // syntax if (c >= l && c <= p) break; // if in range, move on } l = p; if (c == p) // if char matches this element break; // move on } while (p && p != ']') // got a match in char set p = *Pattern++; // skip to end of set break; default: c = *String++; if (tolower(c) != p) // check for exact char return FALSE; // not a match break; } } }