2020-09-30 17:12:29 +02:00

1011 lines
22 KiB
C

#include <windows.h>
#include <stdio.h>
#include <signal.h>
#include <float.h>
#include <time.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>
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;
}
}
}