qbe-reader

A parser for the QBE intermediate language in Rust

git clone https://git.8pit.net/qbe-reader.git

   1use crate::types::*;
   2use crate::util::*;
   3use std::collections::HashMap;
   4
   5use nom::{
   6    branch::alt,
   7    bytes::complete::tag,
   8    character::complete::{alpha1, alphanumeric1, char, none_of},
   9    combinator::{map_res, opt, recognize},
  10    error::{FromExternalError, ParseError},
  11    multi::{many0, many0_count, many1, separated_list1},
  12    number::complete::{double, float},
  13    sequence::{delimited, pair, preceded, terminated, tuple},
  14    IResult,
  15};
  16
  17// STRING := '"' .... '"'
  18fn string(input: &str) -> IResult<&str, String> {
  19    // TODO: Does qbe support escaping in strings?
  20    map_res(
  21        delimited(char('"'), recognize(many0(none_of("\""))), char('"')),
  22        |s| -> Result<String, ()> { Ok(String::from(s)) },
  23    )(input)
  24}
  25
  26// See https://c9x.me/git/qbe.git/tree/parse.c?h=v1.1#n302
  27fn ident(input: &str) -> IResult<&str, String> {
  28    map_res(
  29        recognize(pair(
  30            alt((alpha1, tag("."), tag("_"))),
  31            many0_count(alt((alphanumeric1, tag("$"), tag("."), tag("_")))),
  32        )),
  33        |s| -> Result<String, ()> { Ok(String::from(s)) },
  34    )(input)
  35}
  36
  37// See https://c9x.me/compile/doc/il-v1.1.html#Sigils
  38fn global(input: &str) -> IResult<&str, String> {
  39    preceded(char('$'), ident)(input)
  40}
  41fn userdef(input: &str) -> IResult<&str, String> {
  42    preceded(char(':'), ident)(input)
  43}
  44fn local(input: &str) -> IResult<&str, String> {
  45    preceded(char('%'), ident)(input)
  46}
  47fn label(input: &str) -> IResult<&str, String> {
  48    preceded(char('@'), ident)(input)
  49}
  50
  51////////////////////////////////////////////////////////////////////////
  52
  53// BASETY := 'w' | 'l' | 's' | 'd'
  54fn base_type(input: &str) -> IResult<&str, BaseType> {
  55    alt((
  56        bind(char('w'), BaseType::Word),
  57        bind(char('l'), BaseType::Long),
  58        bind(char('s'), BaseType::Single),
  59        bind(char('d'), BaseType::Double),
  60    ))(input)
  61}
  62
  63// EXTTY := BASETY | 'b' | 'h'
  64fn ext_type(input: &str) -> IResult<&str, ExtType> {
  65    alt((
  66        bind(char('b'), ExtType::Byte),
  67        bind(char('h'), ExtType::Halfword),
  68        map_res(base_type, |ty| -> Result<ExtType, ()> {
  69            Ok(ExtType::Base(ty))
  70        }),
  71    ))(input)
  72}
  73
  74// CONST :=
  75//     ['-'] NUMBER  # Decimal integer
  76//   | 's_' FP       # Single-precision float
  77//   | 'd_' FP       # Double-precision float
  78//   | $IDENT        # Global symbol
  79fn constant(input: &str) -> IResult<&str, Const> {
  80    // TODO: Ensure that floating point parser matches what QBE's parser does.
  81    // See: https://c9x.me/git/qbe.git/tree/parse.c?h=v1.1#n245
  82    alt((
  83        map_res(
  84            tuple((opt(char('-')), ws(parse_i64))),
  85            |(pfx, n)| -> Result<Const, ()> {
  86                match pfx {
  87                    Some(_) => Ok(Const::Number(-n)),
  88                    None => Ok(Const::Number(n)),
  89                }
  90            },
  91        ),
  92        preceded(
  93            tag("s_"),
  94            map_res(float, |f| -> Result<Const, ()> { Ok(Const::SFP(f)) }),
  95        ),
  96        preceded(
  97            tag("d_"),
  98            map_res(double, |f| -> Result<Const, ()> { Ok(Const::DFP(f)) }),
  99        ),
 100        map_res(global, |s| -> Result<Const, ()> { Ok(Const::Global(s)) }),
 101    ))(input)
 102}
 103
 104// DYNCONST :=
 105//     CONST
 106//   | 'thread' $IDENT  # Thread-local symbol
 107fn dynconstant(input: &str) -> IResult<&str, DynConst> {
 108    alt((
 109        map_res(constant, |c| -> Result<DynConst, ()> {
 110            Ok(DynConst::Const(c))
 111        }),
 112        map_res(
 113            preceded(tag("thread"), ws(global)),
 114            |i| -> Result<DynConst, ()> { Ok(DynConst::Thread(i)) },
 115        ),
 116    ))(input)
 117}
 118
 119// LINKAGE :=
 120//     'export'
 121//   | 'thread'
 122//   | 'section' SECNAME
 123//   | 'section' SECNAME SECFLAGS
 124//
 125// SECNAME  := '"' .... '"'
 126// SECFLAGS := '"' .... '"'
 127fn _linkage(input: &str) -> IResult<&str, Linkage> {
 128    alt((
 129        map_res(tag("export"), |_| -> Result<Linkage, ()> {
 130            Ok(Linkage::Export)
 131        }),
 132        map_res(tag("thread"), |_| -> Result<Linkage, ()> {
 133            Ok(Linkage::Thread)
 134        }),
 135        map_res(
 136            tuple((tag("section"), ws(string), opt(string))),
 137            |(_, secname, secflags)| -> Result<Linkage, ()> {
 138                Ok(Linkage::Section(secname, secflags))
 139            },
 140        ),
 141    ))(input)
 142}
 143
 144// Parse linkage terminated with one or more newline characters.
 145fn linkage(input: &str) -> IResult<&str, Linkage> {
 146    terminated(ws(_linkage), newline0)(input)
 147}
 148
 149// TYPEDEF := REGTY | OPAQUETY
 150pub fn typedef(input: &str) -> IResult<&str, TypeDef> {
 151    alt((regty, opaque))(input)
 152}
 153
 154// REGTY :=
 155//     'type' :IDENT '=' ['align' NUMBER]
 156//     '{'
 157//         ( SUBTY [NUMBER] ),
 158//     '}'
 159fn regty(input: &str) -> IResult<&str, TypeDef> {
 160    map_res(
 161        tuple((
 162            tag("type"),
 163            ws(userdef),
 164            char('='),
 165            opt(preceded(ws(tag("align")), parse_u64)),
 166            delimited(ws(char('{')), members, ws(char('}'))),
 167        )),
 168        |(_, id, _, align, members)| -> Result<TypeDef, ()> {
 169            Ok(TypeDef {
 170                name: id,
 171                defn: AggregateType::Regular(align, members),
 172            })
 173        },
 174    )(input)
 175}
 176
 177// SUBTY := EXTTY | :IDENT
 178fn subty(input: &str) -> IResult<&str, SubType> {
 179    alt((
 180        map_res(ext_type, |ty| -> Result<SubType, ()> {
 181            Ok(SubType::ExtType(ty))
 182        }),
 183        map_res(userdef, |id| -> Result<SubType, ()> {
 184            Ok(SubType::UserDef(id))
 185        }),
 186    ))(input)
 187}
 188
 189// MEMBERS := ( MEMBER ),
 190fn members(input: &str) -> IResult<&str, Vec<(SubType, u64)>> {
 191    separated_list1(ws(char(',')), member)(input)
 192}
 193
 194// MEMBER := SUBTY [NUMBER]
 195fn member(input: &str) -> IResult<&str, (SubType, u64)> {
 196    map_res(
 197        pair(subty, opt(ws(parse_u64))),
 198        |(ty, n)| -> Result<(SubType, u64), ()> { Ok((ty, n.unwrap_or(1))) },
 199    )(input)
 200}
 201
 202// OPAQUETY := 'type' :IDENT '=' 'align' NUMBER '{' NUMBER '}'
 203fn opaque(input: &str) -> IResult<&str, TypeDef> {
 204    map_res(
 205        tuple((
 206            tag("type"),
 207            ws(userdef),
 208            char('='),
 209            ws(tag("align")),
 210            parse_u64,
 211            delimited(ws(char('{')), ws(parse_u64), char('}')),
 212        )),
 213        |(_, id, _, _, size, align)| -> Result<TypeDef, ()> {
 214            Ok(TypeDef {
 215                name: id,
 216                defn: AggregateType::Opaque(size, align),
 217            })
 218        },
 219    )(input)
 220}
 221
 222// DATADEF :=
 223//     LINKAGE*
 224//     'data' $IDENT '=' ['align' NUMBER]
 225//     '{'
 226//         ( EXTTY DATAITEM+
 227//         | 'z'   NUMBER ),
 228//     '}'
 229pub fn datadef(input: &str) -> IResult<&str, DataDef> {
 230    map_res(
 231        tuple((
 232            many0(ws(linkage)),
 233            ws(tag("data")),
 234            global,
 235            ws(char('=')),
 236            opt(preceded(tag("align"), parse_u64)),
 237            delimited(ws(char('{')), data_objs, char('}')),
 238        )),
 239        |(lnk, _, ident, _, align, objs)| -> Result<DataDef, ()> {
 240            Ok(DataDef {
 241                linkage: lnk,
 242                name: ident,
 243                align: align,
 244                objs: objs,
 245            })
 246        },
 247    )(input)
 248}
 249
 250// DATAOBJS := ( DATAOBJ ),
 251fn data_objs(input: &str) -> IResult<&str, Vec<DataObj>> {
 252    separated_list1(ws(char(',')), data_obj)(input)
 253}
 254
 255// DATAOBJ := EXTTY DATAITEM+
 256//          | 'z'   NUMBER
 257fn data_obj(input: &str) -> IResult<&str, DataObj> {
 258    alt((
 259        map_res(
 260            pair(ext_type, many1(ws(data_item))),
 261            |(ty, items)| -> Result<DataObj, ()> { Ok(DataObj::DataItem(ty, items)) },
 262        ),
 263        map_res(
 264            preceded(char('z'), ws(parse_u64)),
 265            |n| -> Result<DataObj, ()> { Ok(DataObj::ZeroFill(n)) },
 266        ),
 267    ))(input)
 268}
 269
 270// DATAITEM :=
 271//     $IDENT ['+' NUMBER]  # Symbol and offset
 272//   |  '"' ... '"'         # String
 273//   |  CONST               # Constant
 274fn data_item(input: &str) -> IResult<&str, DataItem> {
 275    alt((
 276        map_res(
 277            pair(global, opt(preceded(opt(ws(char('+'))), ws(parse_u64)))),
 278            |(ident, off)| -> Result<DataItem, ()> { Ok(DataItem::Symbol(ident, off)) },
 279        ),
 280        map_res(string, |s| -> Result<DataItem, ()> {
 281            Ok(DataItem::String(s))
 282        }),
 283        map_res(constant, |c| -> Result<DataItem, ()> {
 284            Ok(DataItem::Const(c))
 285        }),
 286    ))(input)
 287}
 288
 289// FUNCDEF :=
 290//     LINKAGE*
 291//     'function' [ABITY] $IDENT PARAMS [NL]
 292//     '{' NL
 293//         BODY
 294//     '}'
 295pub fn funcdef(input: &str) -> IResult<&str, FuncDef> {
 296    map_res(
 297        tuple((
 298            many0(ws(linkage)),
 299            ws(tag("function")),
 300            opt(abity),
 301            ws(global),
 302            params,
 303            ws(newline0),
 304            delimited(ws(char('{')), preceded(newline1, body), char('}')),
 305        )),
 306        |(lnk, _, ret, id, params, _, body)| -> Result<FuncDef, ()> {
 307            Ok(FuncDef {
 308                linkage: lnk,
 309                name: id,
 310                abity: ret,
 311                params: params,
 312                body: body,
 313            })
 314        },
 315    )(input)
 316}
 317
 318// SUBWTY := 'sb' | 'ub' | 'sh' | 'uh'
 319fn sub_word(input: &str) -> IResult<&str, SubWordType> {
 320    alt((
 321        str("sb", SubWordType::SignedByte),
 322        str("ub", SubWordType::UnsignedHalf),
 323        str("sh", SubWordType::SignedHalf),
 324        str("uh", SubWordType::UnsignedHalf),
 325    ))(input)
 326}
 327
 328fn sub_long(input: &str) -> IResult<&str, SubLongType> {
 329    alt((
 330        str("uw", SubLongType::UnsignedWord),
 331        str("sw", SubLongType::SignedWord),
 332        map_res(sub_word, |ty| -> Result<SubLongType, ()> {
 333            Ok(SubLongType::SubWord(ty))
 334        }),
 335    ))(input)
 336}
 337
 338// See https://c9x.me/compile/doc/il-v1.1.html#Memory
 339fn load_type(input: &str) -> IResult<&str, LoadType> {
 340    alt((
 341        map_res(sub_long, |ty| -> Result<LoadType, ()> {
 342            Ok(LoadType::SubLong(ty))
 343        }),
 344        map_res(base_type, |ty| -> Result<LoadType, ()> {
 345            Ok(LoadType::Base(ty))
 346        }),
 347    ))(input)
 348}
 349
 350// ABITY  := BASETY | SUBWTY | :IDENT
 351fn abity(input: &str) -> IResult<&str, Type> {
 352    alt((
 353        map_res(base_type, |ty| -> Result<Type, ()> { Ok(Type::Base(ty)) }),
 354        map_res(sub_word, |ty| -> Result<Type, ()> {
 355            Ok(Type::SubWordType(ty))
 356        }),
 357        map_res(userdef, |str| -> Result<Type, ()> {
 358            Ok(Type::UserDef(str))
 359        }),
 360    ))(input)
 361}
 362
 363// PARAM :=
 364//     ABITY %IDENT  # Regular parameter
 365//   | 'env' %IDENT  # Environment parameter (first)
 366//   | '...'         # Variadic marker (last)
 367fn param(input: &str) -> IResult<&str, FuncParam> {
 368    // TODO: Ensure that env is the first parameter
 369    // TODO: Ensure that variadic marker is the last parameter
 370    alt((
 371        map_res(
 372            pair(abity, ws(local)),
 373            |(ty, id)| -> Result<FuncParam, ()> { Ok(FuncParam::Regular(ty, id)) },
 374        ),
 375        map_res(
 376            preceded(tag("env"), ws(local)),
 377            |id| -> Result<FuncParam, ()> { Ok(FuncParam::Env(id)) },
 378        ),
 379        map_res(tag("..."), |_| -> Result<FuncParam, ()> {
 380            Ok(FuncParam::Variadic)
 381        }),
 382    ))(input)
 383}
 384
 385// PARAMS = '(' (PARAM), ')'
 386fn _params(input: &str) -> IResult<&str, Vec<FuncParam>> {
 387    separated_list1(ws(char(',')), param)(input)
 388}
 389fn params(input: &str) -> IResult<&str, Vec<FuncParam>> {
 390    delimited(char('('), ws(_params), char(')'))(input)
 391}
 392
 393// BODY = BLOCK+
 394fn body(input: &str) -> IResult<&str, Vec<Block>> {
 395    many1(block)(input)
 396}
 397
 398// BLOCK :=
 399//    @IDENT NL     # Block label
 400//    ( PHI NL )*   # Phi instructions
 401//    ( INST NL )*  # Regular instructions
 402//    JUMP NL       # Jump or return
 403fn block(input: &str) -> IResult<&str, Block> {
 404    map_res(
 405        tuple((
 406            terminated(label, ws(newline1)),
 407            many0(terminated(phi, ws(newline1))),
 408            many0(terminated(stat, ws(newline1))),
 409            opt(terminated(jump, ws(newline1))),
 410        )),
 411        |(label, phis, inst, jump)| -> Result<Block, ()> {
 412            Ok(Block {
 413                label: label,
 414                phi: phis,
 415                inst: inst,
 416                jump: jump,
 417            })
 418        },
 419    )(input)
 420}
 421
 422// JUMP :=
 423//     'jmp' @IDENT               # Unconditional
 424//   | 'jnz' VAL, @IDENT, @IDENT  # Conditional
 425//   | 'ret' [VAL]                # Return
 426//   | 'hlt'                      # Termination
 427fn jump(input: &str) -> IResult<&str, JumpInstr> {
 428    alt((
 429        map_res(
 430            preceded(tag("jmp"), ws(label)),
 431            |l| -> Result<JumpInstr, ()> { Ok(JumpInstr::Jump(l)) },
 432        ),
 433        map_res(
 434            tuple((
 435                tag("jnz"),
 436                ws(value),
 437                char(','),
 438                ws(label),
 439                char(','),
 440                ws(label),
 441            )),
 442            |(_, v, _, l1, _, l2)| -> Result<JumpInstr, ()> { Ok(JumpInstr::Jnz(v, l1, l2)) },
 443        ),
 444        map_res(
 445            preceded(tag("ret"), opt(ws(value))),
 446            |v| -> Result<JumpInstr, ()> { Ok(JumpInstr::Return(v)) },
 447        ),
 448        map_res(tag("hlt"), |_| -> Result<JumpInstr, ()> {
 449            Ok(JumpInstr::Halt)
 450        }),
 451    ))(input)
 452}
 453
 454// TODO: This is not documented in the BNF grammar.
 455fn value(input: &str) -> IResult<&str, Value> {
 456    alt((
 457        map_res(local, |var| -> Result<Value, ()> {
 458            Ok(Value::LocalVar(var))
 459        }),
 460        map_res(dynconstant, |cnst| -> Result<Value, ()> {
 461            Ok(Value::Const(cnst))
 462        }),
 463    ))(input)
 464}
 465
 466// PHI_LABELS := 'phi' ( @IDENT VAL ),
 467pub fn phi_labels(input: &str) -> IResult<&str, HashMap<String, Value>> {
 468    map_res(
 469        preceded(
 470            ws(tag("phi")),
 471            separated_list1(ws(char(',')), pair(label, ws(value))),
 472        ),
 473        |xs: Vec<(String, Value)>| -> Result<HashMap<String, Value>, ()> {
 474            Ok(HashMap::from_iter(xs.into_iter()))
 475        },
 476    )(input)
 477}
 478
 479// PHI := %IDENT '=' BASETY PHI_LABELS
 480pub fn phi(input: &str) -> IResult<&str, Phi> {
 481    map_res(
 482        tuple((local, ws(char('=')), base_type, ws(phi_labels))),
 483        |(dest, _, ty, labels)| -> Result<Phi, ()> {
 484            Ok(Phi {
 485                ident: dest,
 486                base_type: ty,
 487                labels,
 488            })
 489        },
 490    )(input)
 491}
 492
 493// See https://c9x.me/compile/doc/il-v1.1.html#Comparisons
 494pub fn compare_op(input: &str) -> IResult<&str, CmpOp> {
 495    // TODO: Extend this for floating point comparisons.
 496    alt((
 497        str("eq", CmpOp::Eq),
 498        str("ne", CmpOp::Ne),
 499        str("sle", CmpOp::Sle),
 500        str("slt", CmpOp::Slt),
 501        str("sge", CmpOp::Sge),
 502        str("sgt", CmpOp::Sgt),
 503        str("ule", CmpOp::Ule),
 504        str("ult", CmpOp::Ult),
 505        str("uge", CmpOp::Uge),
 506        str("ugt", CmpOp::Ugt),
 507    ))(input)
 508}
 509
 510fn instr_tuple<'a, F: 'a, O, E: ParseError<&'a str> + FromExternalError<&'a str, ()>>(
 511    name: &'a str,
 512    arg1: F,
 513    arg2: F,
 514) -> impl FnMut(&'a str) -> IResult<&'a str, (O, O), E>
 515where
 516    F: Fn(&'a str) -> IResult<&'a str, O, E>,
 517{
 518    pair(preceded(tag(name), ws(arg1)), preceded(char(','), ws(arg2)))
 519}
 520
 521macro_rules! make_instr {
 522    { $NAME:expr, $CONS:expr } => {
 523        map_res(
 524            instr_tuple($NAME, value, value),
 525            |(v1, v2)| -> Result<Instr, ()> {
 526                Ok($CONS(v1, v2))
 527            },
 528        )
 529    };
 530}
 531
 532// ALLOC_ALIGN = 4 | 8 | 16
 533fn alloc_align(input: &str) -> IResult<&str, AllocAlign> {
 534    alt((
 535        str("4", AllocAlign::Word),
 536        str("8", AllocAlign::Long),
 537        str("16", AllocAlign::LongLong),
 538    ))(input)
 539}
 540
 541// See https://c9x.me/compile/doc/il-v1.1.html#Instructions
 542fn instr(input: &str) -> IResult<&str, Instr> {
 543    alt((
 544        make_instr!("add", Instr::Add),
 545        make_instr!("sub", Instr::Sub),
 546        make_instr!("mul", Instr::Mul),
 547        map_res(preceded(tag("neg"), ws(value)), |v| -> Result<Instr, ()> {
 548            Ok(Instr::Neg(v))
 549        }),
 550        make_instr!("udiv", Instr::UDiv),
 551        make_instr!("rem", Instr::Rem),
 552        make_instr!("urem", Instr::URem),
 553        make_instr!("or", Instr::Or),
 554        make_instr!("xor", Instr::Xor),
 555        make_instr!("and", Instr::And),
 556        make_instr!("sar", Instr::Sar),
 557        make_instr!("shr", Instr::Shr),
 558        make_instr!("shl", Instr::Shl),
 559        map_res(
 560            pair(preceded(tag("load"), load_type), ws(value)),
 561            |(ty, addr)| -> Result<Instr, ()> { Ok(Instr::Load(ty, addr)) },
 562        ),
 563        map_res(
 564            pair(preceded(tag("alloc"), alloc_align), ws(parse_u64)),
 565            |(align, amount)| -> Result<Instr, ()> { Ok(Instr::Alloc(align, amount)) },
 566        ),
 567        map_res(
 568            tuple((
 569                char('c'),
 570                compare_op,
 571                base_type,
 572                ws(value),
 573                char(','),
 574                ws(value),
 575            )),
 576            |(_, op, ty, v1, _, v2)| -> Result<Instr, ()> { Ok(Instr::Compare(ty, op, v1, v2)) },
 577        ),
 578        map_res(
 579            tuple((tag("ext"), sub_long, ws(value))),
 580            |(_, ty, v)| -> Result<Instr, ()> { Ok(Instr::Ext(ty, v)) },
 581        ),
 582    ))(input)
 583}
 584
 585// TODO: This is not documented in the BNF grammar.
 586fn stat(input: &str) -> IResult<&str, Statement> {
 587    alt((
 588        map_res(
 589            tuple((local, ws(char('=')), base_type, ws(instr))),
 590            |(dest, _, ty, inst)| -> Result<Statement, ()> {
 591                Ok(Statement::Assign(dest, ty, inst))
 592            },
 593        ),
 594        map_res(
 595            tuple((
 596                preceded(tag("store"), ext_type),
 597                ws(value),
 598                char(','),
 599                ws(value),
 600            )),
 601            |(ty, value, _, addr)| -> Result<Statement, ()> {
 602                let instr = VolatileInstr::Store(ty, value, addr);
 603                Ok(Statement::Volatile(instr))
 604            },
 605        ),
 606        map_res(
 607            tuple((
 608                tag("blit"),
 609                ws(value),
 610                char(','),
 611                ws(value),
 612                char(','),
 613                ws(parse_u64),
 614            )),
 615            |(_, src, _, dst, _, n)| -> Result<Statement, ()> {
 616                let instr = VolatileInstr::Blit(src, dst, n);
 617                Ok(Statement::Volatile(instr))
 618            },
 619        ),
 620        map_res(
 621            tuple((
 622                local,
 623                ws(char('=')),
 624                abity,
 625                ws(tag("call")),
 626                global,
 627                ws(params),
 628            )),
 629            |(dest, _, ty, _, fname, params)| -> Result<Statement, ()> {
 630                Ok(Statement::Call(dest, ty, fname, params))
 631            },
 632        ),
 633    ))(input)
 634}
 635
 636#[cfg(test)]
 637mod tests {
 638    use super::*;
 639
 640    #[test]
 641    fn test_string() {
 642        assert_eq!(string("\"foobar\""), Ok(("", String::from("foobar"))));
 643        assert_eq!(string("\"\""), Ok(("", String::from(""))));
 644    }
 645
 646    #[test]
 647    fn test_linkage() {
 648        // Primitive linkage directives
 649        assert_eq!(linkage("export"), Ok(("", Linkage::Export)));
 650        assert_eq!(linkage("thread"), Ok(("", Linkage::Thread)));
 651
 652        // Section linkage directive
 653        assert_eq!(
 654            linkage("section \"foobar\"\n"),
 655            Ok(("", Linkage::Section(String::from("foobar"), None)))
 656        );
 657        assert_eq!(
 658            linkage(" section \"foo\"	\"bar\" \n"),
 659            Ok((
 660                "",
 661                Linkage::Section(String::from("foo"), Some(String::from("bar")))
 662            ))
 663        );
 664    }
 665
 666    #[test]
 667    fn test_types() {
 668        // Base types
 669        assert_eq!(base_type("w"), Ok(("", BaseType::Word)));
 670        assert_eq!(base_type("l"), Ok(("", BaseType::Long)));
 671        assert_eq!(base_type("s"), Ok(("", BaseType::Single)));
 672        assert_eq!(base_type("d"), Ok(("", BaseType::Double)));
 673
 674        // Extented types
 675        assert_eq!(ext_type("s"), Ok(("", ExtType::Base(BaseType::Single))));
 676        assert_eq!(ext_type("h"), Ok(("", ExtType::Halfword)));
 677    }
 678
 679    #[test]
 680    fn test_ident() {
 681        assert_eq!(ident("_foo"), Ok(("", String::from("_foo"))));
 682        assert_eq!(ident("foobar"), Ok(("", String::from("foobar"))));
 683        assert_eq!(ident("foo$_.23__"), Ok(("", String::from("foo$_.23__"))));
 684    }
 685
 686    #[test]
 687    fn test_constant() {
 688        // Number
 689        assert_eq!(constant("23"), Ok(("", Const::Number(23))));
 690        assert_eq!(constant("-5"), Ok(("", Const::Number(-5))));
 691
 692        // Identifier
 693        assert_eq!(
 694            constant("$foobar"),
 695            Ok(("", Const::Global(String::from("foobar"))))
 696        );
 697        assert_eq!(
 698            constant("$_fo$$r"),
 699            Ok(("", Const::Global(String::from("_fo$$r"))))
 700        );
 701
 702        // Float
 703        assert_eq!(constant("s_23.42"), Ok(("", Const::SFP(23.42))));
 704        assert_eq!(constant("s_23."), Ok(("", Const::SFP(23.))));
 705
 706        // Double
 707        assert_eq!(constant("d_11e-1"), Ok(("", Const::DFP(1.1))));
 708    }
 709
 710    #[test]
 711    fn test_dynconst() {
 712        // Constant
 713        assert_eq!(
 714            dynconstant("-   23"),
 715            Ok(("", DynConst::Const(Const::Number(-23))))
 716        );
 717        assert_eq!(
 718            dynconstant("234223422342"),
 719            Ok(("", DynConst::Const(Const::Number(234223422342))))
 720        );
 721
 722        // Thread-local symbol
 723        assert_eq!(
 724            dynconstant("thread $foo"),
 725            Ok(("", DynConst::Thread(String::from("foo"))))
 726        );
 727        assert_eq!(
 728            dynconstant("thread	$foo"),
 729            Ok(("", DynConst::Thread(String::from("foo"))))
 730        );
 731    }
 732
 733    #[test]
 734    fn test_members() {
 735        assert_eq!(
 736            members("s 23"),
 737            Ok((
 738                "",
 739                vec![(SubType::ExtType(ExtType::Base(BaseType::Single)), 23)]
 740            ))
 741        );
 742
 743        assert_eq!(
 744            members("s, s, d, d"),
 745            Ok((
 746                "",
 747                vec![
 748                    (SubType::ExtType(ExtType::Base(BaseType::Single)), 1),
 749                    (SubType::ExtType(ExtType::Base(BaseType::Single)), 1),
 750                    (SubType::ExtType(ExtType::Base(BaseType::Double)), 1),
 751                    (SubType::ExtType(ExtType::Base(BaseType::Double)), 1),
 752                ]
 753            ))
 754        );
 755    }
 756
 757    #[test]
 758    fn test_aggregate() {
 759        // TODO: Expand
 760        assert_eq!(
 761            typedef("type :fourfloats = { s, s, d, d }"),
 762            Ok((
 763                "",
 764                TypeDef {
 765                    name: String::from("fourfloats"),
 766                    defn: AggregateType::Regular(
 767                        None,
 768                        vec![
 769                            (SubType::ExtType(ExtType::Base(BaseType::Single)), 1),
 770                            (SubType::ExtType(ExtType::Base(BaseType::Single)), 1),
 771                            (SubType::ExtType(ExtType::Base(BaseType::Double)), 1),
 772                            (SubType::ExtType(ExtType::Base(BaseType::Double)), 1),
 773                        ]
 774                    )
 775                }
 776            ))
 777        );
 778
 779        assert_eq!(
 780            typedef("type :opaque = align 16 { 32 }"),
 781            Ok((
 782                "",
 783                TypeDef {
 784                    name: String::from("opaque"),
 785                    defn: AggregateType::Opaque(16, 32),
 786                }
 787            ))
 788        );
 789    }
 790
 791    #[test]
 792    fn test_dataitm() {
 793        // Symbolic with and without offset
 794        assert_eq!(
 795            data_item("$foo"),
 796            Ok(("", DataItem::Symbol(String::from("foo"), None)))
 797        );
 798        assert_eq!(
 799            data_item("$foo +23"),
 800            Ok(("", DataItem::Symbol(String::from("foo"), Some(23))))
 801        );
 802        assert_eq!(
 803            data_item("$foo   +  23"),
 804            Ok(("", DataItem::Symbol(String::from("foo"), Some(23))))
 805        );
 806
 807        // String
 808        assert_eq!(
 809            data_item("\"test\""),
 810            Ok(("", DataItem::String(String::from("test"))))
 811        );
 812        assert_eq!(
 813            data_item("\" f o o\""),
 814            Ok(("", DataItem::String(String::from(" f o o"))))
 815        );
 816
 817        // Constant
 818        assert_eq!(
 819            data_item("23"),
 820            Ok(("", DataItem::Const(Const::Number(23))))
 821        );
 822        assert_eq!(
 823            data_item("s_42"),
 824            Ok(("", DataItem::Const(Const::SFP(42.))))
 825        );
 826    }
 827
 828    #[test]
 829    fn test_dataobj() {
 830        // TODO: Expand
 831        assert_eq!(
 832            data_obj("h 23"),
 833            Ok((
 834                "",
 835                DataObj::DataItem(ExtType::Halfword, vec![DataItem::Const(Const::Number(23))])
 836            ))
 837        );
 838        assert_eq!(data_obj("z   42"), Ok(("", DataObj::ZeroFill(42))));
 839    }
 840
 841    #[test]
 842    fn test_dataobjs() {
 843        // TODO: Expand
 844        assert_eq!(data_objs("z 1337"), Ok(("", vec![DataObj::ZeroFill(1337)])));
 845        assert_eq!(
 846            data_objs("z 42, z 10"),
 847            Ok(("", vec![DataObj::ZeroFill(42), DataObj::ZeroFill(10)]))
 848        );
 849    }
 850
 851    #[test]
 852    fn test_datadef() {
 853        let input = "data $b = { z 1000 }";
 854        let items = vec![DataObj::ZeroFill(1000)];
 855        assert_eq!(
 856            datadef(input),
 857            Ok((
 858                "",
 859                DataDef {
 860                    linkage: vec![],
 861                    name: String::from("b"),
 862                    align: None,
 863                    objs: items
 864                }
 865            ))
 866        );
 867
 868        let input = "data $c = { l -1 2, l $c }";
 869        let items = vec![
 870            DataObj::DataItem(
 871                ExtType::Base(BaseType::Long),
 872                vec![
 873                    DataItem::Const(Const::Number(-1)),
 874                    DataItem::Const(Const::Number(2)),
 875                ],
 876            ),
 877            DataObj::DataItem(
 878                ExtType::Base(BaseType::Long),
 879                vec![DataItem::Symbol(String::from("c"), None)],
 880            ),
 881        ];
 882        assert_eq!(
 883            datadef(input),
 884            Ok((
 885                "",
 886                DataDef {
 887                    linkage: vec![],
 888                    name: String::from("c"),
 889                    align: None,
 890                    objs: items
 891                }
 892            ))
 893        );
 894    }
 895
 896    #[test]
 897    fn test_block() {
 898        assert_eq!(
 899            block("@start\nhlt\n"),
 900            Ok((
 901                "",
 902                Block {
 903                    label: String::from("start"),
 904                    phi: vec![],
 905                    inst: vec![],
 906                    jump: Some(JumpInstr::Halt),
 907                }
 908            ))
 909        );
 910
 911        assert_eq!(
 912            block("@start\n"),
 913            Ok((
 914                "",
 915                Block {
 916                    label: String::from("start"),
 917                    phi: vec![],
 918                    inst: vec![],
 919                    jump: None,
 920                }
 921            ))
 922        );
 923
 924        assert_eq!(
 925            block("@.1\n%.10 =w phi @body.10 0, @logic_right.11 %foo\nret\n"),
 926            Ok((
 927                "",
 928                Block {
 929                    label: String::from(".1"),
 930                    phi: vec![Phi {
 931                        ident: String::from(".10"),
 932                        base_type: BaseType::Word,
 933                        labels: HashMap::from([
 934                            (
 935                                String::from("body.10"),
 936                                Value::Const(DynConst::Const(Const::Number(0)))
 937                            ),
 938                            (
 939                                String::from("logic_right.11"),
 940                                Value::LocalVar(String::from("foo")),
 941                            )
 942                        ])
 943                    }],
 944                    inst: vec![],
 945                    jump: Some(JumpInstr::Return(None)),
 946                }
 947            ))
 948        );
 949    }
 950
 951    #[test]
 952    fn test_stat() {
 953        assert_eq!(
 954            stat("%c =w add %a, %b"),
 955            Ok((
 956                "",
 957                Statement::Assign(
 958                    String::from("c"),
 959                    BaseType::Word,
 960                    Instr::Add(
 961                        Value::LocalVar(String::from("a")),
 962                        Value::LocalVar(String::from("b"))
 963                    )
 964                )
 965            ))
 966        );
 967
 968        assert_eq!(
 969            stat("%r =w ceqw %o1, %o2"),
 970            Ok((
 971                "",
 972                Statement::Assign(
 973                    String::from("r"),
 974                    BaseType::Word,
 975                    Instr::Compare(
 976                        BaseType::Word,
 977                        CmpOp::Eq,
 978                        Value::LocalVar(String::from("o1")),
 979                        Value::LocalVar(String::from("o2"))
 980                    )
 981                )
 982            ))
 983        );
 984
 985        assert_eq!(
 986            stat("blit %s0, %s1, 8"),
 987            Ok((
 988                "",
 989                Statement::Volatile(VolatileInstr::Blit(
 990                    Value::LocalVar(String::from("s0")),
 991                    Value::LocalVar(String::from("s1")),
 992                    8
 993                ))
 994            ))
 995        );
 996
 997        assert_eq!(
 998            stat("%r =s call $myfunc(s %a, l %ap)"),
 999            Ok((
1000                "",
1001                Statement::Call(
1002                    String::from("r"),
1003                    Type::Base(BaseType::Single),
1004                    String::from("myfunc"),
1005                    vec![
1006                        FuncParam::Regular(Type::Base(BaseType::Single), String::from("a")),
1007                        FuncParam::Regular(Type::Base(BaseType::Long), String::from("ap")),
1008                    ],
1009                )
1010            ))
1011        );
1012    }
1013
1014    #[test]
1015    fn test_funcdef() {
1016        let input = "export function w $getone(:one %p) {\n@start\nhlt\n}";
1017        assert_eq!(
1018            funcdef(input),
1019            Ok((
1020                "",
1021                FuncDef {
1022                    linkage: vec![Linkage::Export],
1023                    name: String::from("getone"),
1024                    abity: Some(Type::Base(BaseType::Word)),
1025                    params: vec![FuncParam::Regular(
1026                        Type::UserDef(String::from("one")),
1027                        String::from("p")
1028                    )],
1029                    body: vec![Block {
1030                        label: String::from("start"),
1031                        phi: vec![],
1032                        inst: vec![],
1033                        jump: Some(JumpInstr::Halt),
1034                    }]
1035                }
1036            ))
1037        );
1038    }
1039}