The "level" of a programming language is largely based on a function of the degree to which the language provides abstractions that separate the programmer from the details of the implementation in machine code. A low-level language provides few or no such abstractions; higher-level languages provide increasingly more abstractions that separate the programmer from implementation details.

Obviously, the lowest level language is machine code itself, which has no abstractions at all, and is just sequences of numbers. This is just about as low as it gets. Assembly language provides one level of abstraction: instead of dealing with opcodes directly, the programmer uses mnemonic names for the instructions, which the assembler converts into the opcodes. The assembler will generally also allow the programmer to give memory locations names so that they can be addressed in a symbolic way instead of by their numerical addresses. But the programmer still has to write each machine instruction himself or herself, keep track of what is in what memory location and in what register, and so forth. Assembly is therefore the "middle level" of programming languages.

The next step up introduces abstraction at the instruction level. Instead of specifying the exact instructions to be used, the programmer specifies what operations are to be performed and the compiler figures out the best way to do that. Programmers no longer have to keep track of what is in what register from instruction to instruction; the compiler does that for them. This also allows for programs to be written that do not depend intrinsically on the underlying instruction set of the computer. A program written in FORTRAN (the first language at this level) should run the same way on literally thousands of different architectures (although you'll run into issues with the definition of "number" being different). This is arguably the gravamen of a "high level" programming language.

Unfortunately, once you go past this level (the level at which FORTRAN, COBOL, BASIC, and C all live, along with other relics like PL/I and Modula), it gets confusing, and often argumentative. That's because beyond this baseline, languages add different additional abstractions. Not all further abstractions are compatible with one another, and different abstractions provide different solutions to similar problems. It's not possible to arrange them into a hierarchy. Is Haskell higher level than Prolog? Is Clojure higher level than Ruby? Where does APL fit into this picture? SNOBOL? FORTH? (I've been around a while and have probably forgotten more programming languages than a lot of today's programmers know.) These questions can't be meaningfully answered because the languages are on different branches of a very complicated tree that has lots of interactions between its branches. In some cases you can make a coherent argument, such as when one language is a clear evolution of another, with additional abstraction layers added. So, for example, it is fairly reasonably arguable that C++ and Objective C are higher level than C, because both are evolved from C, and add abstractions that C lacks. But you can't really say which of C++ or Objective C is "higher level". And when dealing with languages that are not well-related to one another (such as, say Clojure and Ruby) it's quite impossible to say that one is "higher level" than the other. All you can do is enumerate the ways that they are similar and different.

So, while I've seen people try to create definitions for levels beyond "low", "middle", and "high", these definition usually reflect value judgments by the person making the definition (e.g. "functional languages are higher level than imperative" or "languages with intrinsic memory management are higher level than those with explicit memory management").